aboutsummaryrefslogtreecommitdiff
path: root/examples/redis-unstable/src/redis-cli.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/redis-unstable/src/redis-cli.c')
-rw-r--r--examples/redis-unstable/src/redis-cli.c11143
1 files changed, 0 insertions, 11143 deletions
diff --git a/examples/redis-unstable/src/redis-cli.c b/examples/redis-unstable/src/redis-cli.c
deleted file mode 100644
index 19627d7..0000000
--- a/examples/redis-unstable/src/redis-cli.c
+++ /dev/null
@@ -1,11143 +0,0 @@
1/* Redis CLI (command line interface)
2 *
3 * Copyright (c) 2009-Present, Redis Ltd.
4 * All rights reserved.
5 *
6 * Licensed under your choice of (a) the Redis Source Available License 2.0
7 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
8 * GNU Affero General Public License v3 (AGPLv3).
9 */
10
11#include "fmacros.h"
12
13#include <stdarg.h>
14#include <stdio.h>
15#include <string.h>
16#include <stdlib.h>
17#include <signal.h>
18#include <unistd.h>
19#include <time.h>
20#include <ctype.h>
21#include <errno.h>
22#include <sys/stat.h>
23#include <sys/time.h>
24#include <assert.h>
25#include <fcntl.h>
26#include <limits.h>
27#include <math.h>
28#include <termios.h>
29
30#include <hiredis.h>
31#ifdef USE_OPENSSL
32#include <openssl/ssl.h>
33#include <openssl/err.h>
34#include <hiredis_ssl.h>
35#endif
36#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
37#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
38#include "dict.h"
39#include "adlist.h"
40#include "zmalloc.h"
41#include "linenoise.h"
42#include "anet.h"
43#include "ae.h"
44#include "connection.h"
45#include "cli_common.h"
46#include "mt19937-64.h"
47#include "cli_commands.h"
48#include "hdr_histogram.h"
49
50#define UNUSED(V) ((void) V)
51
52#define OUTPUT_STANDARD 0
53#define OUTPUT_RAW 1
54#define OUTPUT_CSV 2
55#define OUTPUT_JSON 3
56#define OUTPUT_QUOTED_JSON 4
57#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
58#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
59#define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE"
60#define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
61#define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE"
62#define REDIS_CLI_RCFILE_DEFAULT ".redisclirc"
63#define REDIS_CLI_AUTH_ENV "REDISCLI_AUTH"
64#define REDIS_CLI_CLUSTER_YES_ENV "REDISCLI_CLUSTER_YES"
65
66#define CLUSTER_MANAGER_SLOTS 16384
67#define CLUSTER_MANAGER_PORT_INCR 10000 /* same as CLUSTER_PORT_INCR */
68#define CLUSTER_MANAGER_MIGRATE_TIMEOUT 60000
69#define CLUSTER_MANAGER_MIGRATE_PIPELINE 10
70#define CLUSTER_MANAGER_REBALANCE_THRESHOLD 2
71
72#define CLUSTER_MANAGER_INVALID_HOST_ARG \
73 "[ERR] Invalid arguments: you need to pass either a valid " \
74 "address (ie. 120.0.0.1:7000) or space separated IP " \
75 "and port (ie. 120.0.0.1 7000)\n"
76#define CLUSTER_MANAGER_MODE() (config.cluster_manager_command.name != NULL)
77#define CLUSTER_MANAGER_MASTERS_COUNT(nodes, replicas) ((nodes)/((replicas) + 1))
78#define CLUSTER_MANAGER_COMMAND(n,...) \
79 (redisCommand((n)->context, __VA_ARGS__))
80
81#define CLUSTER_MANAGER_NODE_ARRAY_FREE(array) zfree((array)->alloc)
82
83#define CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, err) \
84 clusterManagerLogErr("Node %s:%d replied with error:\n%s\n", \
85 (n)->ip, (n)->port, (err));
86
87#define clusterManagerLogInfo(...) \
88 clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_INFO,__VA_ARGS__)
89
90#define clusterManagerLogErr(...) \
91 clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_ERR,__VA_ARGS__)
92
93#define clusterManagerLogWarn(...) \
94 clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_WARN,__VA_ARGS__)
95
96#define clusterManagerLogOk(...) \
97 clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_SUCCESS,__VA_ARGS__)
98
99#define CLUSTER_MANAGER_FLAG_MYSELF 1 << 0
100#define CLUSTER_MANAGER_FLAG_SLAVE 1 << 1
101#define CLUSTER_MANAGER_FLAG_FRIEND 1 << 2
102#define CLUSTER_MANAGER_FLAG_NOADDR 1 << 3
103#define CLUSTER_MANAGER_FLAG_DISCONNECT 1 << 4
104#define CLUSTER_MANAGER_FLAG_FAIL 1 << 5
105
106#define CLUSTER_MANAGER_CMD_FLAG_FIX 1 << 0
107#define CLUSTER_MANAGER_CMD_FLAG_SLAVE 1 << 1
108#define CLUSTER_MANAGER_CMD_FLAG_YES 1 << 2
109#define CLUSTER_MANAGER_CMD_FLAG_AUTOWEIGHTS 1 << 3
110#define CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER 1 << 4
111#define CLUSTER_MANAGER_CMD_FLAG_SIMULATE 1 << 5
112#define CLUSTER_MANAGER_CMD_FLAG_REPLACE 1 << 6
113#define CLUSTER_MANAGER_CMD_FLAG_COPY 1 << 7
114#define CLUSTER_MANAGER_CMD_FLAG_COLOR 1 << 8
115#define CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS 1 << 9
116#define CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS 1 << 10
117#define CLUSTER_MANAGER_CMD_FLAG_MASTERS_ONLY 1 << 11
118#define CLUSTER_MANAGER_CMD_FLAG_SLAVES_ONLY 1 << 12
119
120#define CLUSTER_MANAGER_OPT_GETFRIENDS 1 << 0
121#define CLUSTER_MANAGER_OPT_COLD 1 << 1
122#define CLUSTER_MANAGER_OPT_UPDATE 1 << 2
123#define CLUSTER_MANAGER_OPT_QUIET 1 << 6
124#define CLUSTER_MANAGER_OPT_VERBOSE 1 << 7
125
126#define CLUSTER_MANAGER_LOG_LVL_INFO 1
127#define CLUSTER_MANAGER_LOG_LVL_WARN 2
128#define CLUSTER_MANAGER_LOG_LVL_ERR 3
129#define CLUSTER_MANAGER_LOG_LVL_SUCCESS 4
130
131#define CLUSTER_JOIN_CHECK_AFTER 20
132
133#define LOG_COLOR_BOLD "29;1m"
134#define LOG_COLOR_RED "31;1m"
135#define LOG_COLOR_GREEN "32;1m"
136#define LOG_COLOR_YELLOW "33;1m"
137#define LOG_COLOR_RESET "0m"
138
139/* cliConnect() flags. */
140#define CC_FORCE (1<<0) /* Re-connect if already connected. */
141#define CC_QUIET (1<<1) /* Don't log connecting errors. */
142
143/* DNS lookup */
144#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46 */
145
146#define REFRESH_INTERVAL 300 /* milliseconds */
147
148#define IS_TTY_OR_FAKETTY() (isatty(STDOUT_FILENO) || getenv("FAKETTY"))
149
150/* --latency-dist palettes. */
151int spectrum_palette_color_size = 19;
152int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196};
153
154int spectrum_palette_mono_size = 13;
155int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253};
156
157/* The actual palette in use. */
158int *spectrum_palette;
159int spectrum_palette_size;
160
161static int orig_termios_saved = 0;
162static struct termios orig_termios; /* To restore terminal at exit.*/
163
164/* Dict Helpers */
165static uint64_t dictSdsHash(const void *key);
166static int dictSdsKeyCompare(dictCmpCache *cache, const void *key1,
167 const void *key2);
168static void dictSdsDestructor(dict *d, void *val);
169static void dictListDestructor(dict *d, void *val);
170
171/* Cluster Manager Command Info */
172typedef struct clusterManagerCommand {
173 char *name;
174 int argc;
175 char **argv;
176 sds stdin_arg; /* arg from stdin. (-X option) */
177 int flags;
178 int replicas;
179 char *from;
180 char *to;
181 char **weight;
182 int weight_argc;
183 char *master_id;
184 int slots;
185 int timeout;
186 int pipeline;
187 float threshold;
188 char *backup_dir;
189 char *from_user;
190 char *from_pass;
191 int from_askpass;
192} clusterManagerCommand;
193
194static int createClusterManagerCommand(char *cmdname, int argc, char **argv);
195
196
197static redisContext *context;
198static struct config {
199 cliConnInfo conn_info;
200 struct timeval connect_timeout;
201 char *hostsocket;
202 int tls;
203 cliSSLconfig sslconfig;
204 long repeat;
205 long interval;
206 int dbnum; /* db num currently selected */
207 int interactive;
208 int shutdown;
209 int monitor_mode;
210 int pubsub_mode;
211 int blocking_state_aborted; /* used to abort monitor_mode and pubsub_mode. */
212 int vset_recall_mode;
213 sds vset_recall_key;
214 int vset_recall_ele_count;
215 int vset_recall_vsim_count;
216 int vset_recall_vsim_ef;
217 int latency_mode;
218 int latency_dist_mode;
219 int latency_history;
220 int lru_test_mode;
221 long long lru_test_sample_size;
222 int cluster_mode;
223 int cluster_reissue_command;
224 int cluster_send_asking;
225 int slave_mode;
226 int pipe_mode;
227 int pipe_timeout;
228 int getrdb_mode;
229 int get_functions_rdb_mode;
230 int stat_mode;
231 int scan_mode;
232 int count;
233 int intrinsic_latency_mode;
234 int intrinsic_latency_duration;
235 sds pattern;
236 char *rdb_filename;
237 int bigkeys;
238 int memkeys;
239 long long memkeys_samples;
240 int hotkeys;
241 int keystats;
242 unsigned long long cursor;
243 unsigned long top_sizes_limit;
244 int stdin_lastarg; /* get last arg from stdin. (-x option) */
245 int stdin_tag_arg; /* get <tag> arg from stdin. (-X option) */
246 char *stdin_tag_name; /* Placeholder(tag name) for user input. */
247 int askpass;
248 int quoted_input; /* Force input args to be treated as quoted strings */
249 int output; /* output mode, see OUTPUT_* defines */
250 int push_output; /* Should we display spontaneous PUSH replies */
251 sds mb_delim;
252 sds cmd_delim;
253 char prompt[128];
254 char *eval;
255 int eval_ldb;
256 int eval_ldb_sync; /* Ask for synchronous mode of the Lua debugger. */
257 int eval_ldb_end; /* Lua debugging session ended. */
258 int enable_ldb_on_eval; /* Handle manual SCRIPT DEBUG + EVAL commands. */
259 int last_cmd_type;
260 redisReply *last_reply;
261 int verbose;
262 int set_errcode;
263 clusterManagerCommand cluster_manager_command;
264 int no_auth_warning;
265 int resp2; /* value of 1: specified explicitly with option -2 */
266 int resp3; /* value of 1: specified explicitly, value of 2: implicit like --json option */
267 int current_resp3; /* 1 if we have RESP3 right now in the current connection. */
268 int in_multi;
269 int pre_multi_dbnum;
270 char *server_version;
271 char *test_hint;
272 char *test_hint_file;
273 char *client_name;
274 int prefer_ipv4; /* Prefer IPv4 over IPv6 on DNS lookup. */
275 int prefer_ipv6; /* Prefer IPv6 over IPv4 on DNS lookup. */
276} config;
277
278/* User preferences. */
279static struct pref {
280 int hints;
281} pref;
282
283static volatile sig_atomic_t force_cancel_loop = 0;
284static void usage(int err);
285static void slaveMode(int send_sync);
286static int cliConnect(int flags);
287
288static char *getInfoField(char *info, char *field);
289static long getLongInfoField(char *info, char *field);
290
291/*------------------------------------------------------------------------------
292 * Utility functions
293 *--------------------------------------------------------------------------- */
294size_t redis_strlcpy(char *dst, const char *src, size_t dsize);
295
296static void cliPushHandler(void *, void *);
297
298uint16_t crc16(const char *buf, int len);
299
300static long long ustime(void) {
301 struct timeval tv;
302 long long ust;
303
304 gettimeofday(&tv, NULL);
305 ust = ((long long)tv.tv_sec)*1000000;
306 ust += tv.tv_usec;
307 return ust;
308}
309
310static long long mstime(void) {
311 return ustime()/1000;
312}
313
314static void cliRefreshPrompt(void) {
315 if (config.eval_ldb) return;
316
317 sds prompt = sdsempty();
318 if (config.hostsocket != NULL) {
319 prompt = sdscatfmt(prompt,"redis %s",config.hostsocket);
320 } else {
321 char addr[256];
322 formatAddr(addr, sizeof(addr), config.conn_info.hostip, config.conn_info.hostport);
323 prompt = sdscatlen(prompt,addr,strlen(addr));
324 }
325
326 /* Add [dbnum] if needed */
327 if (config.dbnum != 0)
328 prompt = sdscatfmt(prompt,"[%i]",config.dbnum);
329
330 /* Add TX if in transaction state*/
331 if (config.in_multi)
332 prompt = sdscatlen(prompt,"(TX)",4);
333
334 if (config.pubsub_mode)
335 prompt = sdscatfmt(prompt,"(subscribed mode)");
336
337 /* Copy the prompt in the static buffer. */
338 prompt = sdscatlen(prompt,"> ",2);
339 snprintf(config.prompt,sizeof(config.prompt),"%s",prompt);
340 sdsfree(prompt);
341}
342
343/* Return the name of the dotfile for the specified 'dotfilename'.
344 * Normally it just concatenates user $HOME to the file specified
345 * in 'dotfilename'. However if the environment variable 'envoverride'
346 * is set, its value is taken as the path.
347 *
348 * The function returns NULL (if the file is /dev/null or cannot be
349 * obtained for some error), or an SDS string that must be freed by
350 * the user. */
351static sds getDotfilePath(char *envoverride, char *dotfilename) {
352 char *path = NULL;
353 sds dotPath = NULL;
354
355 /* Check the env for a dotfile override. */
356 path = getenv(envoverride);
357 if (path != NULL && *path != '\0') {
358 if (!strcmp("/dev/null", path)) {
359 return NULL;
360 }
361
362 /* If the env is set, return it. */
363 dotPath = sdsnew(path);
364 } else {
365 char *home = getenv("HOME");
366 if (home != NULL && *home != '\0') {
367 /* If no override is set use $HOME/<dotfilename>. */
368 dotPath = sdscatprintf(sdsempty(), "%s/%s", home, dotfilename);
369 }
370 }
371 return dotPath;
372}
373
374static uint64_t dictSdsHash(const void *key) {
375 return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
376}
377
378static int dictSdsKeyCompare(dictCmpCache *cache, const void *key1, const void *key2)
379{
380 int l1,l2;
381 UNUSED(cache);
382
383 l1 = sdslen((sds)key1);
384 l2 = sdslen((sds)key2);
385 if (l1 != l2) return 0;
386 return memcmp(key1, key2, l1) == 0;
387}
388
389static void dictSdsDestructor(dict *d, void *val)
390{
391 UNUSED(d);
392 sdsfree(val);
393}
394
395void dictListDestructor(dict *d, void *val)
396{
397 UNUSED(d);
398 listRelease((list*)val);
399}
400
401/* Erase the lines before printing, and returns the number of lines printed */
402int cleanPrintfln(char *fmt, ...) {
403 va_list args;
404 char buf[1024]; /* limitation */
405 int char_count, line_count = 0;
406
407 /* Clear the line if in TTY */
408 if (IS_TTY_OR_FAKETTY()) {
409 printf("\033[2K\r");
410 }
411
412 va_start(args, fmt);
413 char_count = vsnprintf(buf, sizeof(buf), fmt, args);
414 va_end(args);
415
416 if (char_count >= (int)sizeof(buf)) {
417 fprintf(stderr, "Warning: String was trimmed in cleanPrintln\n");
418 }
419
420 char *position, *string = buf;
421 while ((position = strchr(string, '\n')) != NULL) {
422 int line_length = (int)(position - string);
423 printf("%.*s\n", line_length, string);
424 string = position + 1;
425 line_count++;
426 }
427
428 printf("%s\n", string);
429 return line_count + 1;
430}
431
432/*------------------------------------------------------------------------------
433 * Help functions
434 *--------------------------------------------------------------------------- */
435
436#define CLI_HELP_COMMAND 1
437#define CLI_HELP_GROUP 2
438
439typedef struct {
440 int type;
441 int argc;
442 sds *argv;
443 sds full;
444
445 /* Only used for help on commands */
446 struct commandDocs docs;
447} helpEntry;
448
449static helpEntry *helpEntries = NULL;
450static int helpEntriesLen = 0;
451
452/* For backwards compatibility with pre-7.0 servers.
453 * cliLegacyInitHelp() sets up the helpEntries array with the command and group
454 * names from the commands.c file. However the Redis instance we are connecting
455 * to may support more commands, so this function integrates the previous
456 * entries with additional entries obtained using the COMMAND command
457 * available in recent versions of Redis. */
458static void cliLegacyIntegrateHelp(void) {
459 if (cliConnect(CC_QUIET) == REDIS_ERR) return;
460
461 redisReply *reply = redisCommand(context, "COMMAND");
462 if (reply == NULL) return;
463 if (reply->type != REDIS_REPLY_ARRAY) {
464 freeReplyObject(reply);
465 return;
466 }
467
468 /* Scan the array reported by COMMAND and fill only the entries that
469 * don't already match what we have. */
470 for (size_t j = 0; j < reply->elements; j++) {
471 redisReply *entry = reply->element[j];
472 if (entry->type != REDIS_REPLY_ARRAY || entry->elements < 4 ||
473 entry->element[0]->type != REDIS_REPLY_STRING ||
474 entry->element[1]->type != REDIS_REPLY_INTEGER ||
475 entry->element[3]->type != REDIS_REPLY_INTEGER) return;
476 char *cmdname = entry->element[0]->str;
477 int i;
478
479 for (i = 0; i < helpEntriesLen; i++) {
480 helpEntry *he = helpEntries+i;
481 if (!strcasecmp(he->argv[0],cmdname))
482 break;
483 }
484 if (i != helpEntriesLen) continue;
485
486 helpEntriesLen++;
487 helpEntries = zrealloc(helpEntries,sizeof(helpEntry)*helpEntriesLen);
488 helpEntry *new = helpEntries+(helpEntriesLen-1);
489
490 new->argc = 1;
491 new->argv = zmalloc(sizeof(sds));
492 new->argv[0] = sdsnew(cmdname);
493 new->full = new->argv[0];
494 new->type = CLI_HELP_COMMAND;
495 sdstoupper(new->argv[0]);
496
497 new->docs.name = new->argv[0];
498 new->docs.args = NULL;
499 new->docs.numargs = 0;
500 new->docs.params = sdsempty();
501 int args = llabs(entry->element[1]->integer);
502 args--; /* Remove the command name itself. */
503 if (entry->element[3]->integer == 1) {
504 new->docs.params = sdscat(new->docs.params,"key ");
505 args--;
506 }
507 while(args-- > 0) new->docs.params = sdscat(new->docs.params,"arg ");
508 if (entry->element[1]->integer < 0)
509 new->docs.params = sdscat(new->docs.params,"...options...");
510 new->docs.summary = "Help not available";
511 new->docs.since = "Not known";
512 new->docs.group = "generic";
513 }
514 freeReplyObject(reply);
515}
516
517/* Concatenate a string to an sds string, but if it's empty substitute double quote marks. */
518static sds sdscat_orempty(sds params, const char *value) {
519 if (value[0] == '\0') {
520 return sdscat(params, "\"\"");
521 }
522 return sdscat(params, value);
523}
524
525static sds makeHint(char **inputargv, int inputargc, int cmdlen, struct commandDocs docs);
526
527static void cliAddCommandDocArg(cliCommandArg *cmdArg, redisReply *argMap);
528
529static void cliMakeCommandDocArgs(redisReply *arguments, cliCommandArg *result) {
530 for (size_t j = 0; j < arguments->elements; j++) {
531 cliAddCommandDocArg(&result[j], arguments->element[j]);
532 }
533}
534
535static void cliAddCommandDocArg(cliCommandArg *cmdArg, redisReply *argMap) {
536 if (argMap->type != REDIS_REPLY_MAP && argMap->type != REDIS_REPLY_ARRAY) {
537 return;
538 }
539
540 for (size_t i = 0; i < argMap->elements; i += 2) {
541 assert(argMap->element[i]->type == REDIS_REPLY_STRING);
542 char *key = argMap->element[i]->str;
543 if (!strcmp(key, "name")) {
544 assert(argMap->element[i + 1]->type == REDIS_REPLY_STRING);
545 cmdArg->name = sdsnew(argMap->element[i + 1]->str);
546 } else if (!strcmp(key, "display_text")) {
547 assert(argMap->element[i + 1]->type == REDIS_REPLY_STRING);
548 cmdArg->display_text = sdsnew(argMap->element[i + 1]->str);
549 } else if (!strcmp(key, "token")) {
550 assert(argMap->element[i + 1]->type == REDIS_REPLY_STRING);
551 cmdArg->token = sdsnew(argMap->element[i + 1]->str);
552 } else if (!strcmp(key, "type")) {
553 assert(argMap->element[i + 1]->type == REDIS_REPLY_STRING);
554 char *type = argMap->element[i + 1]->str;
555 if (!strcmp(type, "string")) {
556 cmdArg->type = ARG_TYPE_STRING;
557 } else if (!strcmp(type, "integer")) {
558 cmdArg->type = ARG_TYPE_INTEGER;
559 } else if (!strcmp(type, "double")) {
560 cmdArg->type = ARG_TYPE_DOUBLE;
561 } else if (!strcmp(type, "key")) {
562 cmdArg->type = ARG_TYPE_KEY;
563 } else if (!strcmp(type, "pattern")) {
564 cmdArg->type = ARG_TYPE_PATTERN;
565 } else if (!strcmp(type, "unix-time")) {
566 cmdArg->type = ARG_TYPE_UNIX_TIME;
567 } else if (!strcmp(type, "pure-token")) {
568 cmdArg->type = ARG_TYPE_PURE_TOKEN;
569 } else if (!strcmp(type, "oneof")) {
570 cmdArg->type = ARG_TYPE_ONEOF;
571 } else if (!strcmp(type, "block")) {
572 cmdArg->type = ARG_TYPE_BLOCK;
573 }
574 } else if (!strcmp(key, "arguments")) {
575 redisReply *arguments = argMap->element[i + 1];
576 cmdArg->subargs = zcalloc(arguments->elements * sizeof(cliCommandArg));
577 cmdArg->numsubargs = arguments->elements;
578 cliMakeCommandDocArgs(arguments, cmdArg->subargs);
579 } else if (!strcmp(key, "flags")) {
580 redisReply *flags = argMap->element[i + 1];
581 assert(flags->type == REDIS_REPLY_SET || flags->type == REDIS_REPLY_ARRAY);
582 for (size_t j = 0; j < flags->elements; j++) {
583 assert(flags->element[j]->type == REDIS_REPLY_STATUS);
584 char *flag = flags->element[j]->str;
585 if (!strcmp(flag, "optional")) {
586 cmdArg->flags |= CMD_ARG_OPTIONAL;
587 } else if (!strcmp(flag, "multiple")) {
588 cmdArg->flags |= CMD_ARG_MULTIPLE;
589 } else if (!strcmp(flag, "multiple_token")) {
590 cmdArg->flags |= CMD_ARG_MULTIPLE_TOKEN;
591 }
592 }
593 }
594 }
595}
596
597/* Fill in the fields of a help entry for the command/subcommand name. */
598static void cliFillInCommandHelpEntry(helpEntry *help, char *cmdname, char *subcommandname) {
599 help->argc = subcommandname ? 2 : 1;
600 help->argv = zmalloc(sizeof(sds) * help->argc);
601 help->argv[0] = sdsnew(cmdname);
602 sdstoupper(help->argv[0]);
603 if (subcommandname) {
604 /* Subcommand name may be two words separated by a pipe character. */
605 char *pipe = strchr(subcommandname, '|');
606 if (pipe != NULL) {
607 help->argv[1] = sdsnew(pipe + 1);
608 } else {
609 help->argv[1] = sdsnew(subcommandname);
610 }
611 sdstoupper(help->argv[1]);
612 }
613 sds fullname = sdsnew(help->argv[0]);
614 if (subcommandname) {
615 fullname = sdscat(fullname, " ");
616 fullname = sdscat(fullname, help->argv[1]);
617 }
618 help->full = fullname;
619 help->type = CLI_HELP_COMMAND;
620
621 help->docs.name = help->full;
622 help->docs.params = NULL;
623 help->docs.args = NULL;
624 help->docs.numargs = 0;
625 help->docs.since = NULL;
626}
627
628/* Initialize a command help entry for the command/subcommand described in 'specs'.
629 * 'next' points to the next help entry to be filled in.
630 * 'groups' is a set of command group names to be filled in.
631 * Returns a pointer to the next available position in the help entries table.
632 * If the command has subcommands, this is called recursively for the subcommands.
633 */
634static helpEntry *cliInitCommandHelpEntry(char *cmdname, char *subcommandname,
635 helpEntry *next, redisReply *specs,
636 dict *groups) {
637 helpEntry *help = next++;
638 cliFillInCommandHelpEntry(help, cmdname, subcommandname);
639
640 assert(specs->type == REDIS_REPLY_MAP || specs->type == REDIS_REPLY_ARRAY);
641 for (size_t j = 0; j < specs->elements; j += 2) {
642 assert(specs->element[j]->type == REDIS_REPLY_STRING);
643 char *key = specs->element[j]->str;
644 if (!strcmp(key, "summary")) {
645 redisReply *reply = specs->element[j + 1];
646 assert(reply->type == REDIS_REPLY_STRING);
647 help->docs.summary = sdsnew(reply->str);
648 } else if (!strcmp(key, "since")) {
649 redisReply *reply = specs->element[j + 1];
650 assert(reply->type == REDIS_REPLY_STRING);
651 help->docs.since = sdsnew(reply->str);
652 } else if (!strcmp(key, "group")) {
653 redisReply *reply = specs->element[j + 1];
654 assert(reply->type == REDIS_REPLY_STRING);
655 help->docs.group = sdsnew(reply->str);
656 sds group = sdsdup(help->docs.group);
657 if (dictAdd(groups, group, NULL) != DICT_OK) {
658 sdsfree(group);
659 }
660 } else if (!strcmp(key, "arguments")) {
661 redisReply *arguments = specs->element[j + 1];
662 assert(arguments->type == REDIS_REPLY_ARRAY);
663 help->docs.args = zcalloc(arguments->elements * sizeof(cliCommandArg));
664 help->docs.numargs = arguments->elements;
665 cliMakeCommandDocArgs(arguments, help->docs.args);
666 help->docs.params = makeHint(NULL, 0, 0, help->docs);
667 } else if (!strcmp(key, "subcommands")) {
668 redisReply *subcommands = specs->element[j + 1];
669 assert(subcommands->type == REDIS_REPLY_MAP || subcommands->type == REDIS_REPLY_ARRAY);
670 for (size_t i = 0; i < subcommands->elements; i += 2) {
671 assert(subcommands->element[i]->type == REDIS_REPLY_STRING);
672 char *subcommandname = subcommands->element[i]->str;
673 redisReply *subcommand = subcommands->element[i + 1];
674 assert(subcommand->type == REDIS_REPLY_MAP || subcommand->type == REDIS_REPLY_ARRAY);
675 next = cliInitCommandHelpEntry(cmdname, subcommandname, next, subcommand, groups);
676 }
677 }
678 }
679 return next;
680}
681
682/* Returns the total number of commands and subcommands in the command docs table. */
683static size_t cliCountCommands(redisReply* commandTable) {
684 size_t numCommands = commandTable->elements / 2;
685
686 /* The command docs table maps command names to a map of their specs. */
687 for (size_t i = 0; i < commandTable->elements; i += 2) {
688 assert(commandTable->element[i]->type == REDIS_REPLY_STRING); /* Command name. */
689 assert(commandTable->element[i + 1]->type == REDIS_REPLY_MAP ||
690 commandTable->element[i + 1]->type == REDIS_REPLY_ARRAY);
691 redisReply *map = commandTable->element[i + 1];
692 for (size_t j = 0; j < map->elements; j += 2) {
693 assert(map->element[j]->type == REDIS_REPLY_STRING);
694 char *key = map->element[j]->str;
695 if (!strcmp(key, "subcommands")) {
696 redisReply *subcommands = map->element[j + 1];
697 assert(subcommands->type == REDIS_REPLY_MAP || subcommands->type == REDIS_REPLY_ARRAY);
698 numCommands += subcommands->elements / 2;
699 }
700 }
701 }
702 return numCommands;
703}
704
705/* Comparator for sorting help table entries. */
706int helpEntryCompare(const void *entry1, const void *entry2) {
707 helpEntry *i1 = (helpEntry *)entry1;
708 helpEntry *i2 = (helpEntry *)entry2;
709 return strcmp(i1->full, i2->full);
710}
711
712/* Initializes command help entries for command groups.
713 * Called after the command help entries have already been filled in.
714 * Extends the help table with new entries for the command groups.
715 */
716void cliInitGroupHelpEntries(dict *groups) {
717 dictIterator iter;
718 dictEntry *entry;
719 helpEntry tmp;
720
721 int numGroups = dictSize(groups);
722 int pos = helpEntriesLen;
723 helpEntriesLen += numGroups;
724 helpEntries = zrealloc(helpEntries, sizeof(helpEntry)*helpEntriesLen);
725
726 dictInitIterator(&iter, groups);
727 for (entry = dictNext(&iter); entry != NULL; entry = dictNext(&iter)) {
728 tmp.argc = 1;
729 tmp.argv = zmalloc(sizeof(sds));
730 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",(char *)dictGetKey(entry));
731 tmp.full = tmp.argv[0];
732 tmp.type = CLI_HELP_GROUP;
733 tmp.docs.name = NULL;
734 tmp.docs.params = NULL;
735 tmp.docs.args = NULL;
736 tmp.docs.numargs = 0;
737 tmp.docs.summary = NULL;
738 tmp.docs.since = NULL;
739 tmp.docs.group = NULL;
740 helpEntries[pos++] = tmp;
741 }
742 dictResetIterator(&iter);
743}
744
745/* Initializes help entries for all commands in the COMMAND DOCS reply. */
746void cliInitCommandHelpEntries(redisReply *commandTable, dict *groups) {
747 helpEntry *next = helpEntries;
748 for (size_t i = 0; i < commandTable->elements; i += 2) {
749 assert(commandTable->element[i]->type == REDIS_REPLY_STRING);
750 char *cmdname = commandTable->element[i]->str;
751
752 assert(commandTable->element[i + 1]->type == REDIS_REPLY_MAP ||
753 commandTable->element[i + 1]->type == REDIS_REPLY_ARRAY);
754 redisReply *cmdspecs = commandTable->element[i + 1];
755 next = cliInitCommandHelpEntry(cmdname, NULL, next, cmdspecs, groups);
756 }
757}
758
759/* Does the server version support a command/argument only available "since" some version?
760 * Returns 1 when supported, or 0 when the "since" version is newer than "version". */
761static int versionIsSupported(sds version, sds since) {
762 int i;
763 char *versionPos = version;
764 char *sincePos = since;
765 if (!since) {
766 return 1;
767 }
768
769 for (i = 0; i != 3; i++) {
770 int versionPart = atoi(versionPos);
771 int sincePart = atoi(sincePos);
772 if (versionPart > sincePart) {
773 return 1;
774 } else if (sincePart > versionPart) {
775 return 0;
776 }
777 versionPos = strchr(versionPos, '.');
778 sincePos = strchr(sincePos, '.');
779
780 /* If we finished to parse both `version` and `since`, it means they are equal */
781 if (!versionPos && !sincePos) return 1;
782
783 /* Different number of digits considered as not supported */
784 if (!versionPos || !sincePos) return 0;
785
786 versionPos++;
787 sincePos++;
788 }
789 return 0;
790}
791
792static void removeUnsupportedArgs(struct cliCommandArg *args, int *numargs, sds version) {
793 int i = 0, j;
794 while (i != *numargs) {
795 if (versionIsSupported(version, args[i].since)) {
796 if (args[i].subargs) {
797 removeUnsupportedArgs(args[i].subargs, &args[i].numsubargs, version);
798 }
799 i++;
800 continue;
801 }
802 for (j = i; j != *numargs - 1; j++) {
803 args[j] = args[j + 1];
804 }
805 (*numargs)--;
806 }
807}
808
809static helpEntry *cliLegacyInitCommandHelpEntry(char *cmdname, char *subcommandname,
810 helpEntry *next, struct commandDocs *command,
811 dict *groups, sds version) {
812 helpEntry *help = next++;
813 cliFillInCommandHelpEntry(help, cmdname, subcommandname);
814
815 help->docs.summary = sdsnew(command->summary);
816 help->docs.since = sdsnew(command->since);
817 help->docs.group = sdsnew(command->group);
818 sds group = sdsdup(help->docs.group);
819 if (dictAdd(groups, group, NULL) != DICT_OK) {
820 sdsfree(group);
821 }
822
823 if (command->args != NULL) {
824 help->docs.args = command->args;
825 help->docs.numargs = command->numargs;
826 if (version)
827 removeUnsupportedArgs(help->docs.args, &help->docs.numargs, version);
828 help->docs.params = makeHint(NULL, 0, 0, help->docs);
829 }
830
831 if (command->subcommands != NULL) {
832 for (size_t i = 0; command->subcommands[i].name != NULL; i++) {
833 if (!version || versionIsSupported(version, command->subcommands[i].since)) {
834 char *subcommandname = command->subcommands[i].name;
835 next = cliLegacyInitCommandHelpEntry(
836 cmdname, subcommandname, next, &command->subcommands[i], groups, version);
837 }
838 }
839 }
840 return next;
841}
842
843int cliLegacyInitCommandHelpEntries(struct commandDocs *commands, dict *groups, sds version) {
844 helpEntry *next = helpEntries;
845 for (size_t i = 0; commands[i].name != NULL; i++) {
846 if (!version || versionIsSupported(version, commands[i].since)) {
847 next = cliLegacyInitCommandHelpEntry(commands[i].name, NULL, next, &commands[i], groups, version);
848 }
849 }
850 return next - helpEntries;
851}
852
853/* Returns the total number of commands and subcommands in the command docs table,
854 * filtered by server version (if provided).
855 */
856static size_t cliLegacyCountCommands(struct commandDocs *commands, sds version) {
857 int numCommands = 0;
858 for (size_t i = 0; commands[i].name != NULL; i++) {
859 if (version && !versionIsSupported(version, commands[i].since)) {
860 continue;
861 }
862 numCommands++;
863 if (commands[i].subcommands != NULL) {
864 numCommands += cliLegacyCountCommands(commands[i].subcommands, version);
865 }
866 }
867 return numCommands;
868}
869
870/* Gets the server version string by calling INFO SERVER.
871 * Stores the result in config.server_version.
872 * When not connected, or not possible, returns NULL. */
873static sds cliGetServerVersion(void) {
874 static const char *key = "\nredis_version:";
875 redisReply *serverInfo = NULL;
876 char *pos;
877
878 if (config.server_version != NULL) {
879 return config.server_version;
880 }
881
882 if (!context) return NULL;
883 serverInfo = redisCommand(context, "INFO SERVER");
884 if (serverInfo == NULL || serverInfo->type == REDIS_REPLY_ERROR) {
885 freeReplyObject(serverInfo);
886 return sdsempty();
887 }
888
889 assert(serverInfo->type == REDIS_REPLY_STRING || serverInfo->type == REDIS_REPLY_VERB);
890 sds info = serverInfo->str;
891
892 /* Finds the first appearance of "redis_version" in the INFO SERVER reply. */
893 pos = strstr(info, key);
894 if (pos) {
895 pos += strlen(key);
896 char *end = strchr(pos, '\r');
897 if (end) {
898 sds version = sdsnewlen(pos, end - pos);
899 freeReplyObject(serverInfo);
900 config.server_version = version;
901 return version;
902 }
903 }
904 freeReplyObject(serverInfo);
905 return NULL;
906}
907
908static void cliLegacyInitHelp(dict *groups) {
909 sds serverVersion = cliGetServerVersion();
910
911 /* Scan the commandDocs array and fill in the entries */
912 helpEntriesLen = cliLegacyCountCommands(redisCommandTable, serverVersion);
913 helpEntries = zmalloc(sizeof(helpEntry)*helpEntriesLen);
914
915 helpEntriesLen = cliLegacyInitCommandHelpEntries(redisCommandTable, groups, serverVersion);
916 cliInitGroupHelpEntries(groups);
917
918 qsort(helpEntries, helpEntriesLen, sizeof(helpEntry), helpEntryCompare);
919 dictRelease(groups);
920}
921
922/* cliInitHelp() sets up the helpEntries array with the command and group
923 * names and command descriptions obtained using the COMMAND DOCS command.
924 */
925static void cliInitHelp(void) {
926 /* Dict type for a set of strings, used to collect names of command groups. */
927 dictType groupsdt = {
928 dictSdsHash, /* hash function */
929 NULL, /* key dup */
930 NULL, /* val dup */
931 dictSdsKeyCompare, /* key compare */
932 dictSdsDestructor, /* key destructor */
933 NULL, /* val destructor */
934 NULL /* allow to expand */
935 };
936 redisReply *commandTable;
937 dict *groups;
938
939 if (cliConnect(CC_QUIET) == REDIS_ERR) {
940 /* Can not connect to the server, but we still want to provide
941 * help, generate it only from the static cli_commands.c data instead. */
942 groups = dictCreate(&groupsdt);
943 cliLegacyInitHelp(groups);
944 return;
945 }
946 commandTable = redisCommand(context, "COMMAND DOCS");
947 if (commandTable == NULL || commandTable->type == REDIS_REPLY_ERROR) {
948 /* New COMMAND DOCS subcommand not supported - generate help from
949 * static cli_commands.c data instead. */
950 freeReplyObject(commandTable);
951
952 groups = dictCreate(&groupsdt);
953 cliLegacyInitHelp(groups);
954 cliLegacyIntegrateHelp();
955 return;
956 };
957 if (commandTable->type != REDIS_REPLY_MAP && commandTable->type != REDIS_REPLY_ARRAY) {
958 freeReplyObject(commandTable);
959 return;
960 }
961
962 /* Scan the array reported by COMMAND DOCS and fill in the entries */
963 helpEntriesLen = cliCountCommands(commandTable);
964 helpEntries = zmalloc(sizeof(helpEntry)*helpEntriesLen);
965
966 groups = dictCreate(&groupsdt);
967 cliInitCommandHelpEntries(commandTable, groups);
968 cliInitGroupHelpEntries(groups);
969
970 qsort(helpEntries, helpEntriesLen, sizeof(helpEntry), helpEntryCompare);
971 freeReplyObject(commandTable);
972 dictRelease(groups);
973}
974
975/* Output command help to stdout. */
976static void cliOutputCommandHelp(struct commandDocs *help, int group) {
977 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
978 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
979 if (help->since != NULL) {
980 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since);
981 }
982 if (group) {
983 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", help->group);
984 }
985}
986
987/* Print generic help. */
988static void cliOutputGenericHelp(void) {
989 sds version = cliVersion();
990 printf(
991 "redis-cli %s\n"
992 "To get help about Redis commands type:\n"
993 " \"help @<group>\" to get a list of commands in <group>\n"
994 " \"help <command>\" for help on <command>\n"
995 " \"help <tab>\" to get a list of possible help topics\n"
996 " \"quit\" to exit\n"
997 "\n"
998 "To set redis-cli preferences:\n"
999 " \":set hints\" enable online hints\n"
1000 " \":set nohints\" disable online hints\n"
1001 "Set your preferences in ~/.redisclirc\n",
1002 version
1003 );
1004 sdsfree(version);
1005}
1006
1007/* Output all command help, filtering by group or command name. */
1008static void cliOutputHelp(int argc, char **argv) {
1009 int i, j;
1010 char *group = NULL;
1011 helpEntry *entry;
1012 struct commandDocs *help;
1013
1014 if (argc == 0) {
1015 cliOutputGenericHelp();
1016 return;
1017 } else if (argc > 0 && argv[0][0] == '@') {
1018 group = argv[0]+1;
1019 }
1020
1021 if (helpEntries == NULL) {
1022 /* Initialize the help using the results of the COMMAND command.
1023 * In case we are using redis-cli help XXX, we need to init it. */
1024 cliInitHelp();
1025 }
1026
1027 assert(argc > 0);
1028 for (i = 0; i < helpEntriesLen; i++) {
1029 entry = &helpEntries[i];
1030 if (entry->type != CLI_HELP_COMMAND) continue;
1031
1032 help = &entry->docs;
1033 if (group == NULL) {
1034 /* Compare all arguments */
1035 if (argc <= entry->argc) {
1036 for (j = 0; j < argc; j++) {
1037 if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
1038 }
1039 if (j == argc) {
1040 cliOutputCommandHelp(help,1);
1041 }
1042 }
1043 } else if (strcasecmp(group, help->group) == 0) {
1044 cliOutputCommandHelp(help,0);
1045 }
1046 }
1047 printf("\r\n");
1048}
1049
1050/* Linenoise completion callback. */
1051static void completionCallback(const char *buf, linenoiseCompletions *lc) {
1052 size_t startpos = 0;
1053 int mask;
1054 int i;
1055 size_t matchlen;
1056 sds tmp;
1057
1058 if (strncasecmp(buf,"help ",5) == 0) {
1059 startpos = 5;
1060 while (isspace(buf[startpos])) startpos++;
1061 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
1062 } else {
1063 mask = CLI_HELP_COMMAND;
1064 }
1065
1066 for (i = 0; i < helpEntriesLen; i++) {
1067 if (!(helpEntries[i].type & mask)) continue;
1068
1069 matchlen = strlen(buf+startpos);
1070 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
1071 tmp = sdsnewlen(buf,startpos);
1072 tmp = sdscat(tmp,helpEntries[i].full);
1073 linenoiseAddCompletion(lc,tmp);
1074 sdsfree(tmp);
1075 }
1076 }
1077}
1078
1079static sds addHintForArgument(sds hint, cliCommandArg *arg);
1080
1081/* Adds a separator character between words of a string under construction.
1082 * A separator is added if the string length is greater than its previously-recorded
1083 * length (*len), which is then updated, and it's not the last word to be added.
1084 */
1085static sds addSeparator(sds str, size_t *len, char *separator, int is_last) {
1086 if (sdslen(str) > *len && !is_last) {
1087 str = sdscat(str, separator);
1088 *len = sdslen(str);
1089 }
1090 return str;
1091}
1092
1093/* Recursively zeros the matched* fields of all arguments. */
1094static void clearMatchedArgs(cliCommandArg *args, int numargs) {
1095 for (int i = 0; i != numargs; ++i) {
1096 args[i].matched = 0;
1097 args[i].matched_token = 0;
1098 args[i].matched_name = 0;
1099 args[i].matched_all = 0;
1100 if (args[i].subargs) {
1101 clearMatchedArgs(args[i].subargs, args[i].numsubargs);
1102 }
1103 }
1104}
1105
1106/* Builds a completion hint string describing the arguments, skipping parts already matched.
1107 * Hints for all arguments are added to the input 'hint' parameter, separated by 'separator'.
1108 */
1109static sds addHintForArguments(sds hint, cliCommandArg *args, int numargs, char *separator) {
1110 int i, j, incomplete;
1111 size_t len=sdslen(hint);
1112 for (i = 0; i < numargs; i++) {
1113 if (!(args[i].flags & CMD_ARG_OPTIONAL)) {
1114 hint = addHintForArgument(hint, &args[i]);
1115 hint = addSeparator(hint, &len, separator, i == numargs-1);
1116 continue;
1117 }
1118
1119 /* The rule is that successive "optional" arguments can appear in any order.
1120 * But if they are followed by a required argument, no more of those optional arguments
1121 * can appear after that.
1122 *
1123 * This code handles all successive optional args together. This lets us show the
1124 * completion of the currently-incomplete optional arg first, if there is one.
1125 */
1126 for (j = i, incomplete = -1; j < numargs; j++) {
1127 if (!(args[j].flags & CMD_ARG_OPTIONAL)) break;
1128 if (args[j].matched != 0 && args[j].matched_all == 0) {
1129 /* User has started typing this arg; show its completion first. */
1130 hint = addHintForArgument(hint, &args[j]);
1131 hint = addSeparator(hint, &len, separator, i == numargs-1);
1132 incomplete = j;
1133 }
1134 }
1135
1136 /* If the following non-optional arg has not been matched, add hints for
1137 * any remaining optional args in this group.
1138 */
1139 if (j == numargs || args[j].matched == 0) {
1140 for (; i < j; i++) {
1141 if (incomplete != i) {
1142 hint = addHintForArgument(hint, &args[i]);
1143 hint = addSeparator(hint, &len, separator, i == numargs-1);
1144 }
1145 }
1146 }
1147
1148 i = j - 1;
1149 }
1150 return hint;
1151}
1152
1153/* Adds the "repeating" section of the hint string for a multiple-typed argument: [ABC def ...]
1154 * The repeating part is a fixed unit; we don't filter matched elements from it.
1155 */
1156static sds addHintForRepeatedArgument(sds hint, cliCommandArg *arg) {
1157 if (!(arg->flags & CMD_ARG_MULTIPLE)) {
1158 return hint;
1159 }
1160
1161 /* The repeating part is always shown at the end of the argument's hint,
1162 * so we can safely clear its matched flags before printing it.
1163 */
1164 clearMatchedArgs(arg, 1);
1165
1166 if (hint[0] != '\0') {
1167 hint = sdscat(hint, " ");
1168 }
1169 hint = sdscat(hint, "[");
1170
1171 if (arg->flags & CMD_ARG_MULTIPLE_TOKEN) {
1172 hint = sdscat_orempty(hint, arg->token);
1173 if (arg->type != ARG_TYPE_PURE_TOKEN) {
1174 hint = sdscat(hint, " ");
1175 }
1176 }
1177
1178 switch (arg->type) {
1179 case ARG_TYPE_ONEOF:
1180 hint = addHintForArguments(hint, arg->subargs, arg->numsubargs, "|");
1181 break;
1182
1183 case ARG_TYPE_BLOCK:
1184 hint = addHintForArguments(hint, arg->subargs, arg->numsubargs, " ");
1185 break;
1186
1187 case ARG_TYPE_PURE_TOKEN:
1188 break;
1189
1190 default:
1191 hint = sdscat_orempty(hint, arg->display_text ? arg->display_text : arg->name);
1192 break;
1193 }
1194
1195 hint = sdscat(hint, " ...]");
1196 return hint;
1197}
1198
1199/* Adds hint string for one argument, if not already matched. */
1200static sds addHintForArgument(sds hint, cliCommandArg *arg) {
1201 if (arg->matched_all) {
1202 return hint;
1203 }
1204
1205 /* Surround an optional arg with brackets, unless it's partially matched. */
1206 if ((arg->flags & CMD_ARG_OPTIONAL) && !arg->matched) {
1207 hint = sdscat(hint, "[");
1208 }
1209
1210 /* Start with the token, if present and not matched. */
1211 if (arg->token != NULL && !arg->matched_token) {
1212 hint = sdscat_orempty(hint, arg->token);
1213 if (arg->type != ARG_TYPE_PURE_TOKEN) {
1214 hint = sdscat(hint, " ");
1215 }
1216 }
1217
1218 /* Add the body of the syntax string. */
1219 switch (arg->type) {
1220 case ARG_TYPE_ONEOF:
1221 if (arg->matched == 0) {
1222 hint = addHintForArguments(hint, arg->subargs, arg->numsubargs, "|");
1223 } else {
1224 int i;
1225 for (i = 0; i < arg->numsubargs; i++) {
1226 if (arg->subargs[i].matched != 0) {
1227 hint = addHintForArgument(hint, &arg->subargs[i]);
1228 }
1229 }
1230 }
1231 break;
1232
1233 case ARG_TYPE_BLOCK:
1234 hint = addHintForArguments(hint, arg->subargs, arg->numsubargs, " ");
1235 break;
1236
1237 case ARG_TYPE_PURE_TOKEN:
1238 break;
1239
1240 default:
1241 if (!arg->matched_name) {
1242 hint = sdscat_orempty(hint, arg->display_text ? arg->display_text : arg->name);
1243 }
1244 break;
1245 }
1246
1247 hint = addHintForRepeatedArgument(hint, arg);
1248
1249 if ((arg->flags & CMD_ARG_OPTIONAL) && !arg->matched) {
1250 hint = sdscat(hint, "]");
1251 }
1252
1253 return hint;
1254}
1255
1256static int matchArg(char **nextword, int numwords, cliCommandArg *arg);
1257static int matchArgs(char **words, int numwords, cliCommandArg *args, int numargs);
1258
1259/* Tries to match the next words of the input against an argument. */
1260static int matchNoTokenArg(char **nextword, int numwords, cliCommandArg *arg) {
1261 int i;
1262 switch (arg->type) {
1263 case ARG_TYPE_BLOCK: {
1264 arg->matched += matchArgs(nextword, numwords, arg->subargs, arg->numsubargs);
1265
1266 /* All the subargs must be matched for the block to match. */
1267 arg->matched_all = 1;
1268 for (i = 0; i < arg->numsubargs; i++) {
1269 if (arg->subargs[i].matched_all == 0) {
1270 arg->matched_all = 0;
1271 }
1272 }
1273 break;
1274 }
1275 case ARG_TYPE_ONEOF: {
1276 for (i = 0; i < arg->numsubargs; i++) {
1277 if (matchArg(nextword, numwords, &arg->subargs[i])) {
1278 arg->matched += arg->subargs[i].matched;
1279 arg->matched_all = arg->subargs[i].matched_all;
1280 break;
1281 }
1282 }
1283 break;
1284 }
1285
1286 case ARG_TYPE_INTEGER:
1287 case ARG_TYPE_UNIX_TIME: {
1288 long long value;
1289 if (sscanf(*nextword, "%lld", &value) == 1) {
1290 arg->matched += 1;
1291 arg->matched_name = 1;
1292 arg->matched_all = 1;
1293 } else {
1294 /* Matching failed due to incorrect arg type. */
1295 arg->matched = 0;
1296 arg->matched_name = 0;
1297 }
1298 break;
1299 }
1300
1301 case ARG_TYPE_DOUBLE: {
1302 double value;
1303 if (sscanf(*nextword, "%lf", &value) == 1) {
1304 arg->matched += 1;
1305 arg->matched_name = 1;
1306 arg->matched_all = 1;
1307 } else {
1308 /* Matching failed due to incorrect arg type. */
1309 arg->matched = 0;
1310 arg->matched_name = 0;
1311 }
1312 break;
1313 }
1314
1315 default:
1316 arg->matched += 1;
1317 arg->matched_name = 1;
1318 arg->matched_all = 1;
1319 break;
1320 }
1321 return arg->matched;
1322}
1323
1324/* Tries to match the next word of the input against a token literal. */
1325static int matchToken(char **nextword, cliCommandArg *arg) {
1326 if (strcasecmp(arg->token, nextword[0]) != 0) {
1327 return 0;
1328 }
1329 arg->matched_token = 1;
1330 arg->matched = 1;
1331 return 1;
1332}
1333
1334/* Tries to match the next words of the input against the next argument.
1335 * If the arg is repeated ("multiple"), it will be matched only once.
1336 * If the next input word(s) can't be matched, returns 0 for failure.
1337 */
1338static int matchArgOnce(char **nextword, int numwords, cliCommandArg *arg) {
1339 /* First match the token, if present. */
1340 if (arg->token != NULL) {
1341 if (!matchToken(nextword, arg)) {
1342 return 0;
1343 }
1344 if (arg->type == ARG_TYPE_PURE_TOKEN) {
1345 arg->matched_all = 1;
1346 return 1;
1347 }
1348 if (numwords == 1) {
1349 return 1;
1350 }
1351 nextword++;
1352 numwords--;
1353 }
1354
1355 /* Then match the rest of the argument. */
1356 if (!matchNoTokenArg(nextword, numwords, arg)) {
1357 return 0;
1358 }
1359 return arg->matched;
1360}
1361
1362/* Tries to match the next words of the input against the next argument.
1363 * If the arg is repeated ("multiple"), it will be matched as many times as possible.
1364 */
1365static int matchArg(char **nextword, int numwords, cliCommandArg *arg) {
1366 int matchedWords = 0;
1367 int matchedOnce = matchArgOnce(nextword, numwords, arg);
1368 if (!(arg->flags & CMD_ARG_MULTIPLE)) {
1369 return matchedOnce;
1370 }
1371
1372 /* Found one match; now match a "multiple" argument as many times as possible. */
1373 matchedWords += matchedOnce;
1374 while (arg->matched_all && matchedWords < numwords) {
1375 clearMatchedArgs(arg, 1);
1376 if (arg->token != NULL && !(arg->flags & CMD_ARG_MULTIPLE_TOKEN)) {
1377 /* The token only appears the first time; the rest of the times,
1378 * pretend we saw it so we don't hint it.
1379 */
1380 matchedOnce = matchNoTokenArg(nextword + matchedWords, numwords - matchedWords, arg);
1381 if (arg->matched) {
1382 arg->matched_token = 1;
1383 }
1384 } else {
1385 matchedOnce = matchArgOnce(nextword + matchedWords, numwords - matchedWords, arg);
1386 }
1387 matchedWords += matchedOnce;
1388 }
1389 arg->matched_all = 0; /* Because more repetitions are still possible. */
1390 return matchedWords;
1391}
1392
1393/* Tries to match the next words of the input against
1394 * any one of a consecutive set of optional arguments.
1395 */
1396static int matchOneOptionalArg(char **words, int numwords, cliCommandArg *args, int numargs, int *matchedarg) {
1397 for (int nextword = 0, nextarg = 0; nextword != numwords && nextarg != numargs; ++nextarg) {
1398 if (args[nextarg].matched) {
1399 /* Already matched this arg. */
1400 continue;
1401 }
1402
1403 int matchedWords = matchArg(&words[nextword], numwords - nextword, &args[nextarg]);
1404 if (matchedWords != 0) {
1405 *matchedarg = nextarg;
1406 return matchedWords;
1407 }
1408 }
1409 return 0;
1410}
1411
1412/* Matches as many input words as possible against a set of consecutive optional arguments. */
1413static int matchOptionalArgs(char **words, int numwords, cliCommandArg *args, int numargs) {
1414 int nextword = 0;
1415 int matchedarg = -1, lastmatchedarg = -1;
1416 while (nextword != numwords) {
1417 int matchedWords = matchOneOptionalArg(&words[nextword], numwords - nextword, args, numargs, &matchedarg);
1418 if (matchedWords == 0) {
1419 break;
1420 }
1421 /* Successfully matched an optional arg; mark any previous match as completed
1422 * so it won't be partially hinted.
1423 */
1424 if (lastmatchedarg != -1) {
1425 args[lastmatchedarg].matched_all = 1;
1426 }
1427 lastmatchedarg = matchedarg;
1428 nextword += matchedWords;
1429 }
1430 return nextword;
1431}
1432
1433/* Matches as many input words as possible against command arguments. */
1434static int matchArgs(char **words, int numwords, cliCommandArg *args, int numargs) {
1435 int nextword, nextarg, matchedWords;
1436 for (nextword = 0, nextarg = 0; nextword != numwords && nextarg != numargs; ++nextarg) {
1437 /* Optional args can occur in any order. Collect a range of consecutive optional args
1438 * and try to match them as a group against the next input words.
1439 */
1440 if (args[nextarg].flags & CMD_ARG_OPTIONAL) {
1441 int lastoptional;
1442 for (lastoptional = nextarg; lastoptional < numargs; lastoptional++) {
1443 if (!(args[lastoptional].flags & CMD_ARG_OPTIONAL)) break;
1444 }
1445 matchedWords = matchOptionalArgs(&words[nextword], numwords - nextword, &args[nextarg], lastoptional - nextarg);
1446 nextarg = lastoptional - 1;
1447 } else {
1448 matchedWords = matchArg(&words[nextword], numwords - nextword, &args[nextarg]);
1449 if (matchedWords == 0) {
1450 /* Couldn't match a required word - matching fails! */
1451 return 0;
1452 }
1453 }
1454
1455 nextword += matchedWords;
1456 }
1457 return nextword;
1458}
1459
1460/* Compute the linenoise hint for the input prefix in inputargv/inputargc.
1461 * cmdlen is the number of words from the start of the input that make up the command.
1462 * If docs.args exists, dynamically creates a hint string by matching the arg specs
1463 * against the input words.
1464 */
1465static sds makeHint(char **inputargv, int inputargc, int cmdlen, struct commandDocs docs) {
1466 sds hint;
1467
1468 if (docs.args) {
1469 /* Remove arguments from the returned hint to show only the
1470 * ones the user did not yet type. */
1471 clearMatchedArgs(docs.args, docs.numargs);
1472 hint = sdsempty();
1473 int matchedWords = 0;
1474 if (inputargv && inputargc)
1475 matchedWords = matchArgs(inputargv + cmdlen, inputargc - cmdlen, docs.args, docs.numargs);
1476 if (matchedWords == inputargc - cmdlen) {
1477 hint = addHintForArguments(hint, docs.args, docs.numargs, " ");
1478 }
1479 return hint;
1480 }
1481
1482 /* If arg specs are not available, show the hint string until the user types something. */
1483 if (inputargc <= cmdlen) {
1484 hint = sdsnew(docs.params);
1485 } else {
1486 hint = sdsempty();
1487 }
1488 return hint;
1489}
1490
1491/* Search for a command matching the longest possible prefix of input words. */
1492static helpEntry* findHelpEntry(int argc, char **argv) {
1493 helpEntry *entry = NULL;
1494 int i, rawargc, matchlen = 0;
1495 sds *rawargv;
1496
1497 for (i = 0; i < helpEntriesLen; i++) {
1498 if (!(helpEntries[i].type & CLI_HELP_COMMAND)) continue;
1499
1500 rawargv = helpEntries[i].argv;
1501 rawargc = helpEntries[i].argc;
1502 if (rawargc <= argc) {
1503 int j;
1504 for (j = 0; j < rawargc; j++) {
1505 if (strcasecmp(rawargv[j],argv[j])) {
1506 break;
1507 }
1508 }
1509 if (j == rawargc && rawargc > matchlen) {
1510 matchlen = rawargc;
1511 entry = &helpEntries[i];
1512 }
1513 }
1514 }
1515 return entry;
1516}
1517
1518/* Returns the command-line hint string for a given partial input. */
1519static sds getHintForInput(const char *charinput) {
1520 sds hint = NULL;
1521 int inputargc, inputlen = strlen(charinput);
1522 sds *inputargv = sdssplitargs(charinput, &inputargc);
1523 int endspace = inputlen && isspace(charinput[inputlen-1]);
1524
1525 /* Don't match the last word until the user has typed a space after it. */
1526 int matchargc = endspace ? inputargc : inputargc - 1;
1527
1528 helpEntry *entry = findHelpEntry(matchargc, inputargv);
1529 if (entry) {
1530 hint = makeHint(inputargv, matchargc, entry->argc, entry->docs);
1531 }
1532 sdsfreesplitres(inputargv, inputargc);
1533 return hint;
1534}
1535
1536/* Linenoise hints callback. */
1537static char *hintsCallback(const char *buf, int *color, int *bold) {
1538 if (!pref.hints) return NULL;
1539
1540 sds hint = getHintForInput(buf);
1541 if (hint == NULL) {
1542 return NULL;
1543 }
1544
1545 *color = 90;
1546 *bold = 0;
1547
1548 /* Add an initial space if needed. */
1549 int len = strlen(buf);
1550 int endspace = len && isspace(buf[len-1]);
1551 if (!endspace) {
1552 sds newhint = sdsnewlen(" ",1);
1553 newhint = sdscatsds(newhint,hint);
1554 sdsfree(hint);
1555 hint = newhint;
1556 }
1557
1558 return hint;
1559}
1560
1561static void freeHintsCallback(void *ptr) {
1562 sdsfree(ptr);
1563}
1564
1565/*------------------------------------------------------------------------------
1566 * TTY manipulation
1567 *--------------------------------------------------------------------------- */
1568
1569/* Restore terminal if we've changed it. */
1570void cliRestoreTTY(void) {
1571 if (orig_termios_saved)
1572 tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
1573}
1574
1575/* Put the terminal in "press any key" mode */
1576static void cliPressAnyKeyTTY(void) {
1577 if (!isatty(STDIN_FILENO)) return;
1578 if (!orig_termios_saved) {
1579 if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) return;
1580 atexit(cliRestoreTTY);
1581 orig_termios_saved = 1;
1582 }
1583 struct termios mode = orig_termios;
1584 mode.c_lflag &= ~(ECHO | ICANON); /* echoing off, canonical off */
1585 tcsetattr(STDIN_FILENO, TCSANOW, &mode);
1586}
1587
1588/*------------------------------------------------------------------------------
1589 * Networking / parsing
1590 *--------------------------------------------------------------------------- */
1591
1592/* Send AUTH command to the server */
1593static int cliAuth(redisContext *ctx, char *user, char *auth) {
1594 redisReply *reply;
1595 if (auth == NULL) return REDIS_OK;
1596
1597 if (user == NULL)
1598 reply = redisCommand(ctx,"AUTH %s",auth);
1599 else
1600 reply = redisCommand(ctx,"AUTH %s %s",user,auth);
1601
1602 if (reply == NULL) {
1603 fprintf(stderr, "\nI/O error\n");
1604 return REDIS_ERR;
1605 }
1606
1607 int result = REDIS_OK;
1608 if (reply->type == REDIS_REPLY_ERROR) {
1609 result = REDIS_ERR;
1610 fprintf(stderr, "AUTH failed: %s\n", reply->str);
1611 }
1612 freeReplyObject(reply);
1613 return result;
1614}
1615
1616/* Send SELECT input_dbnum to the server */
1617static int cliSelect(void) {
1618 redisReply *reply;
1619 if (config.conn_info.input_dbnum == config.dbnum) return REDIS_OK;
1620
1621 reply = redisCommand(context,"SELECT %d",config.conn_info.input_dbnum);
1622 if (reply == NULL) {
1623 fprintf(stderr, "\nI/O error\n");
1624 return REDIS_ERR;
1625 }
1626
1627 int result = REDIS_OK;
1628 if (reply->type == REDIS_REPLY_ERROR) {
1629 result = REDIS_ERR;
1630 fprintf(stderr,"SELECT %d failed: %s\n",config.conn_info.input_dbnum,reply->str);
1631 } else {
1632 config.dbnum = config.conn_info.input_dbnum;
1633 cliRefreshPrompt();
1634 }
1635 freeReplyObject(reply);
1636 return result;
1637}
1638
1639/* Select RESP3 mode if redis-cli was started with the -3 option. */
1640static int cliSwitchProto(void) {
1641 redisReply *reply;
1642 if (!config.resp3 || config.resp2) return REDIS_OK;
1643
1644 reply = redisCommand(context,"HELLO 3");
1645 if (reply == NULL) {
1646 fprintf(stderr, "\nI/O error\n");
1647 return REDIS_ERR;
1648 }
1649
1650 int result = REDIS_OK;
1651 if (reply->type == REDIS_REPLY_ERROR) {
1652 fprintf(stderr,"HELLO 3 failed: %s\n",reply->str);
1653 if (config.resp3 == 1) {
1654 result = REDIS_ERR;
1655 } else if (config.resp3 == 2) {
1656 result = REDIS_OK;
1657 }
1658 }
1659
1660 /* Retrieve server version string for later use. */
1661 for (size_t i = 0; i < reply->elements; i += 2) {
1662 assert(reply->element[i]->type == REDIS_REPLY_STRING);
1663 char *key = reply->element[i]->str;
1664 if (!strcmp(key, "version")) {
1665 assert(reply->element[i + 1]->type == REDIS_REPLY_STRING);
1666 config.server_version = sdsnew(reply->element[i + 1]->str);
1667 }
1668 }
1669 freeReplyObject(reply);
1670 config.current_resp3 = 1;
1671 return result;
1672}
1673
1674/* Set the client name if configured. */
1675static int cliSetName(void) {
1676 if (config.client_name == NULL) return REDIS_OK;
1677
1678 redisReply *reply = redisCommand(context,"CLIENT SETNAME %s", config.client_name);
1679 if (reply == NULL) {
1680 fprintf(stderr, "\nI/O error\n");
1681 return REDIS_ERR;
1682 }
1683 int result = REDIS_OK;
1684 if (reply->type == REDIS_REPLY_ERROR) {
1685 fprintf(stderr,"CLIENT SETNAME failed: %s\n", reply->str);
1686 result = REDIS_ERR;
1687 }
1688 freeReplyObject(reply);
1689 return result;
1690}
1691
1692/* Connect to the server. It is possible to pass certain flags to the function:
1693 * CC_FORCE: The connection is performed even if there is already
1694 * a connected socket.
1695 * CC_QUIET: Don't print errors if connection fails. */
1696static int cliConnect(int flags) {
1697 if (context == NULL || flags & CC_FORCE) {
1698 if (context != NULL) {
1699 redisFree(context);
1700 config.dbnum = 0;
1701 config.in_multi = 0;
1702 config.pubsub_mode = 0;
1703 cliRefreshPrompt();
1704 }
1705
1706 /* Do not use hostsocket when we got redirected in cluster mode */
1707 if (config.hostsocket == NULL ||
1708 (config.cluster_mode && config.cluster_reissue_command)) {
1709 context = redisConnectWrapper(config.conn_info.hostip, config.conn_info.hostport,
1710 config.connect_timeout);
1711 } else {
1712 context = redisConnectUnixWrapper(config.hostsocket, config.connect_timeout);
1713 }
1714
1715 if (!context->err && config.tls) {
1716 const char *err = NULL;
1717 if (cliSecureConnection(context, config.sslconfig, &err) == REDIS_ERR && err) {
1718 fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err);
1719 redisFree(context);
1720 context = NULL;
1721 return REDIS_ERR;
1722 }
1723 }
1724
1725 if (context->err) {
1726 if (!(flags & CC_QUIET)) {
1727 fprintf(stderr,"Could not connect to Redis at ");
1728 if (config.hostsocket == NULL ||
1729 (config.cluster_mode && config.cluster_reissue_command))
1730 {
1731 fprintf(stderr, "%s:%d: %s\n",
1732 config.conn_info.hostip,config.conn_info.hostport,context->errstr);
1733 } else {
1734 fprintf(stderr,"%s: %s\n",
1735 config.hostsocket,context->errstr);
1736 }
1737 }
1738 redisFree(context);
1739 context = NULL;
1740 return REDIS_ERR;
1741 }
1742
1743
1744 /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
1745 * in order to prevent timeouts caused by the execution of long
1746 * commands. At the same time this improves the detection of real
1747 * errors. */
1748 anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
1749
1750 /* State of the current connection. */
1751 config.current_resp3 = 0;
1752
1753 /* Do AUTH, select the right DB, switch to RESP3 if needed. */
1754 if (cliAuth(context, config.conn_info.user, config.conn_info.auth) != REDIS_OK)
1755 return REDIS_ERR;
1756 if (cliSelect() != REDIS_OK)
1757 return REDIS_ERR;
1758 if (cliSwitchProto() != REDIS_OK)
1759 return REDIS_ERR;
1760 if (cliSetName() != REDIS_OK)
1761 return REDIS_ERR;
1762 }
1763
1764 /* Set a PUSH handler if configured to do so. */
1765 if (config.push_output) {
1766 redisSetPushCallback(context, cliPushHandler);
1767 }
1768
1769 return REDIS_OK;
1770}
1771
1772/* In cluster, if server replies ASK, we will redirect to a different node.
1773 * Before sending the real command, we need to send ASKING command first. */
1774static int cliSendAsking(void) {
1775 redisReply *reply;
1776
1777 config.cluster_send_asking = 0;
1778 if (context == NULL) {
1779 return REDIS_ERR;
1780 }
1781 reply = redisCommand(context,"ASKING");
1782 if (reply == NULL) {
1783 fprintf(stderr, "\nI/O error\n");
1784 return REDIS_ERR;
1785 }
1786 int result = REDIS_OK;
1787 if (reply->type == REDIS_REPLY_ERROR) {
1788 result = REDIS_ERR;
1789 fprintf(stderr,"ASKING failed: %s\n",reply->str);
1790 }
1791 freeReplyObject(reply);
1792 return result;
1793}
1794
1795static void cliPrintContextError(void) {
1796 if (context == NULL) return;
1797 fprintf(stderr,"Error: %s\n",context->errstr);
1798}
1799
1800static int isInvalidateReply(redisReply *reply) {
1801 return reply->type == REDIS_REPLY_PUSH && reply->elements == 2 &&
1802 reply->element[0]->type == REDIS_REPLY_STRING &&
1803 !strncmp(reply->element[0]->str, "invalidate", 10) &&
1804 reply->element[1]->type == REDIS_REPLY_ARRAY;
1805}
1806
1807/* Special display handler for RESP3 'invalidate' messages.
1808 * This function does not validate the reply, so it should
1809 * already be confirmed correct */
1810static sds cliFormatInvalidateTTY(redisReply *r) {
1811 sds out = sdsnew("-> invalidate: ");
1812
1813 for (size_t i = 0; i < r->element[1]->elements; i++) {
1814 redisReply *key = r->element[1]->element[i];
1815 assert(key->type == REDIS_REPLY_STRING);
1816
1817 out = sdscatfmt(out, "'%s'", key->str, key->len);
1818 if (i < r->element[1]->elements - 1)
1819 out = sdscatlen(out, ", ", 2);
1820 }
1821
1822 return sdscatlen(out, "\n", 1);
1823}
1824
1825/* Returns non-zero if cliFormatReplyTTY renders the reply in multiple lines. */
1826static int cliIsMultilineValueTTY(redisReply *r) {
1827 switch (r->type) {
1828 case REDIS_REPLY_ARRAY:
1829 case REDIS_REPLY_SET:
1830 case REDIS_REPLY_PUSH:
1831 if (r->elements == 0) return 0;
1832 if (r->elements > 1) return 1;
1833 return cliIsMultilineValueTTY(r->element[0]);
1834 case REDIS_REPLY_MAP:
1835 if (r->elements == 0) return 0;
1836 if (r->elements > 2) return 1;
1837 return cliIsMultilineValueTTY(r->element[1]);
1838 default:
1839 return 0;
1840 }
1841}
1842
1843static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
1844 sds out = sdsempty();
1845 switch (r->type) {
1846 case REDIS_REPLY_ERROR:
1847 out = sdscatprintf(out,"(error) %s\n", r->str);
1848 break;
1849 case REDIS_REPLY_STATUS:
1850 out = sdscat(out,r->str);
1851 out = sdscat(out,"\n");
1852 break;
1853 case REDIS_REPLY_INTEGER:
1854 out = sdscatprintf(out,"(integer) %lld\n",r->integer);
1855 break;
1856 case REDIS_REPLY_DOUBLE:
1857 out = sdscatprintf(out,"(double) %s\n",r->str);
1858 break;
1859 case REDIS_REPLY_STRING:
1860 case REDIS_REPLY_VERB:
1861 /* If you are producing output for the standard output we want
1862 * a more interesting output with quoted characters and so forth,
1863 * unless it's a verbatim string type. */
1864 if (r->type == REDIS_REPLY_STRING) {
1865 out = sdscatrepr(out,r->str,r->len);
1866 out = sdscat(out,"\n");
1867 } else {
1868 out = sdscatlen(out,r->str,r->len);
1869 out = sdscat(out,"\n");
1870 }
1871 break;
1872 case REDIS_REPLY_NIL:
1873 out = sdscat(out,"(nil)\n");
1874 break;
1875 case REDIS_REPLY_BOOL:
1876 out = sdscat(out,r->integer ? "(true)\n" : "(false)\n");
1877 break;
1878 case REDIS_REPLY_ARRAY:
1879 case REDIS_REPLY_MAP:
1880 case REDIS_REPLY_SET:
1881 case REDIS_REPLY_PUSH:
1882 if (r->elements == 0) {
1883 if (r->type == REDIS_REPLY_ARRAY)
1884 out = sdscat(out,"(empty array)\n");
1885 else if (r->type == REDIS_REPLY_MAP)
1886 out = sdscat(out,"(empty hash)\n");
1887 else if (r->type == REDIS_REPLY_SET)
1888 out = sdscat(out,"(empty set)\n");
1889 else if (r->type == REDIS_REPLY_PUSH)
1890 out = sdscat(out,"(empty push)\n");
1891 else
1892 out = sdscat(out,"(empty aggregate type)\n");
1893 } else {
1894 unsigned int i, idxlen = 0;
1895 char _prefixlen[16];
1896 char _prefixfmt[16];
1897 sds _prefix;
1898 sds tmp;
1899
1900 /* Calculate chars needed to represent the largest index */
1901 i = r->elements;
1902 if (r->type == REDIS_REPLY_MAP) i /= 2;
1903 do {
1904 idxlen++;
1905 i /= 10;
1906 } while(i);
1907
1908 /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
1909 memset(_prefixlen,' ',idxlen+2);
1910 _prefixlen[idxlen+2] = '\0';
1911 _prefix = sdscat(sdsnew(prefix),_prefixlen);
1912
1913 /* Setup prefix format for every entry */
1914 char numsep;
1915 if (r->type == REDIS_REPLY_SET) numsep = '~';
1916 else if (r->type == REDIS_REPLY_MAP) numsep = '#';
1917 /* TODO: this would be a breaking change for scripts, do that in a major version. */
1918 /* else if (r->type == REDIS_REPLY_PUSH) numsep = '>'; */
1919 else numsep = ')';
1920 snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud%c ",idxlen,numsep);
1921
1922 for (i = 0; i < r->elements; i++) {
1923 unsigned int human_idx = (r->type == REDIS_REPLY_MAP) ?
1924 i/2 : i;
1925 human_idx++; /* Make it 1-based. */
1926
1927 /* Don't use the prefix for the first element, as the parent
1928 * caller already prepended the index number. */
1929 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,human_idx);
1930
1931 /* Format the multi bulk entry */
1932 tmp = cliFormatReplyTTY(r->element[i],_prefix);
1933 out = sdscatlen(out,tmp,sdslen(tmp));
1934 sdsfree(tmp);
1935
1936 /* For maps, format the value as well. */
1937 if (r->type == REDIS_REPLY_MAP) {
1938 i++;
1939 sdsrange(out,0,-2);
1940 out = sdscat(out," => ");
1941 if (cliIsMultilineValueTTY(r->element[i])) {
1942 /* linebreak before multiline value to fix alignment */
1943 out = sdscat(out, "\n");
1944 out = sdscat(out, _prefix);
1945 }
1946 tmp = cliFormatReplyTTY(r->element[i],_prefix);
1947 out = sdscatlen(out,tmp,sdslen(tmp));
1948 sdsfree(tmp);
1949 }
1950 }
1951 sdsfree(_prefix);
1952 }
1953 break;
1954 default:
1955 fprintf(stderr,"Unknown reply type: %d\n", r->type);
1956 exit(1);
1957 }
1958 return out;
1959}
1960
1961/* Returns 1 if the reply is a pubsub pushed reply. */
1962int isPubsubPush(redisReply *r) {
1963 if (r == NULL ||
1964 r->type != (config.current_resp3 ? REDIS_REPLY_PUSH : REDIS_REPLY_ARRAY) ||
1965 r->elements < 3 ||
1966 r->element[0]->type != REDIS_REPLY_STRING)
1967 {
1968 return 0;
1969 }
1970 char *str = r->element[0]->str;
1971 size_t len = r->element[0]->len;
1972 /* Check if it is [p|s][un]subscribe or [p|s]message, but even simpler, we
1973 * just check that it ends with "message" or "subscribe". */
1974 return ((len >= strlen("message") &&
1975 !strcmp(str + len - strlen("message"), "message")) ||
1976 (len >= strlen("subscribe") &&
1977 !strcmp(str + len - strlen("subscribe"), "subscribe")));
1978}
1979
1980int isColorTerm(void) {
1981 char *t = getenv("TERM");
1982 return t != NULL && strstr(t,"xterm") != NULL;
1983}
1984
1985/* Helper function for sdsCatColorizedLdbReply() appending colorize strings
1986 * to an SDS string. */
1987sds sdscatcolor(sds o, char *s, size_t len, char *color) {
1988 if (!isColorTerm()) return sdscatlen(o,s,len);
1989
1990 int bold = strstr(color,"bold") != NULL;
1991 int ccode = 37; /* Defaults to white. */
1992 if (strstr(color,"red")) ccode = 31;
1993 else if (strstr(color,"green")) ccode = 32;
1994 else if (strstr(color,"yellow")) ccode = 33;
1995 else if (strstr(color,"blue")) ccode = 34;
1996 else if (strstr(color,"magenta")) ccode = 35;
1997 else if (strstr(color,"cyan")) ccode = 36;
1998 else if (strstr(color,"white")) ccode = 37;
1999
2000 o = sdscatfmt(o,"\033[%i;%i;49m",bold,ccode);
2001 o = sdscatlen(o,s,len);
2002 o = sdscat(o,"\033[0m");
2003 return o;
2004}
2005
2006/* Colorize Lua debugger status replies according to the prefix they
2007 * have. */
2008sds sdsCatColorizedLdbReply(sds o, char *s, size_t len) {
2009 char *color = "white";
2010
2011 if (strstr(s,"<debug>")) color = "bold";
2012 if (strstr(s,"<redis>")) color = "green";
2013 if (strstr(s,"<reply>")) color = "cyan";
2014 if (strstr(s,"<error>")) color = "red";
2015 if (strstr(s,"<hint>")) color = "bold";
2016 if (strstr(s,"<value>") || strstr(s,"<retval>")) color = "magenta";
2017 if (len > 4 && isdigit(s[3])) {
2018 if (s[1] == '>') color = "yellow"; /* Current line. */
2019 else if (s[2] == '#') color = "bold"; /* Break point. */
2020 }
2021 return sdscatcolor(o,s,len,color);
2022}
2023
2024static sds cliFormatReplyRaw(redisReply *r) {
2025 sds out = sdsempty(), tmp;
2026 size_t i;
2027
2028 switch (r->type) {
2029 case REDIS_REPLY_NIL:
2030 /* Nothing... */
2031 break;
2032 case REDIS_REPLY_ERROR:
2033 out = sdscatlen(out,r->str,r->len);
2034 out = sdscatlen(out,"\n",1);
2035 break;
2036 case REDIS_REPLY_STATUS:
2037 case REDIS_REPLY_STRING:
2038 case REDIS_REPLY_VERB:
2039 if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
2040 /* The Lua debugger replies with arrays of simple (status)
2041 * strings. We colorize the output for more fun if this
2042 * is a debugging session. */
2043
2044 /* Detect the end of a debugging session. */
2045 if (strstr(r->str,"<endsession>") == r->str) {
2046 config.enable_ldb_on_eval = 0;
2047 config.eval_ldb = 0;
2048 config.eval_ldb_end = 1; /* Signal the caller session ended. */
2049 config.output = OUTPUT_STANDARD;
2050 cliRefreshPrompt();
2051 } else {
2052 out = sdsCatColorizedLdbReply(out,r->str,r->len);
2053 }
2054 } else {
2055 out = sdscatlen(out,r->str,r->len);
2056 }
2057 break;
2058 case REDIS_REPLY_BOOL:
2059 out = sdscat(out,r->integer ? "(true)" : "(false)");
2060 break;
2061 case REDIS_REPLY_INTEGER:
2062 out = sdscatprintf(out,"%lld",r->integer);
2063 break;
2064 case REDIS_REPLY_DOUBLE:
2065 out = sdscatprintf(out,"%s",r->str);
2066 break;
2067 case REDIS_REPLY_SET:
2068 case REDIS_REPLY_ARRAY:
2069 case REDIS_REPLY_PUSH:
2070 for (i = 0; i < r->elements; i++) {
2071 if (i > 0) out = sdscat(out,config.mb_delim);
2072 tmp = cliFormatReplyRaw(r->element[i]);
2073 out = sdscatlen(out,tmp,sdslen(tmp));
2074 sdsfree(tmp);
2075 }
2076 break;
2077 case REDIS_REPLY_MAP:
2078 for (i = 0; i < r->elements; i += 2) {
2079 if (i > 0) out = sdscat(out,config.mb_delim);
2080 tmp = cliFormatReplyRaw(r->element[i]);
2081 out = sdscatlen(out,tmp,sdslen(tmp));
2082 sdsfree(tmp);
2083
2084 out = sdscatlen(out," ",1);
2085 tmp = cliFormatReplyRaw(r->element[i+1]);
2086 out = sdscatlen(out,tmp,sdslen(tmp));
2087 sdsfree(tmp);
2088 }
2089 break;
2090 default:
2091 fprintf(stderr,"Unknown reply type: %d\n", r->type);
2092 exit(1);
2093 }
2094 return out;
2095}
2096
2097static sds cliFormatReplyCSV(redisReply *r) {
2098 unsigned int i;
2099
2100 sds out = sdsempty();
2101 switch (r->type) {
2102 case REDIS_REPLY_ERROR:
2103 out = sdscat(out,"ERROR,");
2104 out = sdscatrepr(out,r->str,strlen(r->str));
2105 break;
2106 case REDIS_REPLY_STATUS:
2107 out = sdscatrepr(out,r->str,r->len);
2108 break;
2109 case REDIS_REPLY_INTEGER:
2110 out = sdscatprintf(out,"%lld",r->integer);
2111 break;
2112 case REDIS_REPLY_DOUBLE:
2113 out = sdscatprintf(out,"%s",r->str);
2114 break;
2115 case REDIS_REPLY_STRING:
2116 case REDIS_REPLY_VERB:
2117 out = sdscatrepr(out,r->str,r->len);
2118 break;
2119 case REDIS_REPLY_NIL:
2120 out = sdscat(out,"NULL");
2121 break;
2122 case REDIS_REPLY_BOOL:
2123 out = sdscat(out,r->integer ? "true" : "false");
2124 break;
2125 case REDIS_REPLY_ARRAY:
2126 case REDIS_REPLY_SET:
2127 case REDIS_REPLY_PUSH:
2128 case REDIS_REPLY_MAP: /* CSV has no map type, just output flat list. */
2129 for (i = 0; i < r->elements; i++) {
2130 sds tmp = cliFormatReplyCSV(r->element[i]);
2131 out = sdscatlen(out,tmp,sdslen(tmp));
2132 if (i != r->elements-1) out = sdscat(out,",");
2133 sdsfree(tmp);
2134 }
2135 break;
2136 default:
2137 fprintf(stderr,"Unknown reply type: %d\n", r->type);
2138 exit(1);
2139 }
2140 return out;
2141}
2142
2143/* Append specified buffer to out and return it, using required JSON output
2144 * mode. */
2145static sds jsonStringOutput(sds out, const char *p, int len, int mode) {
2146 if (mode == OUTPUT_JSON) {
2147 return escapeJsonString(out, p, len);
2148 } else if (mode == OUTPUT_QUOTED_JSON) {
2149 /* Need to double-quote backslashes */
2150 sds tmp = sdscatrepr(sdsempty(), p, len);
2151 int tmplen = sdslen(tmp);
2152 char *n = tmp;
2153 while (tmplen--) {
2154 if (*n == '\\') out = sdscatlen(out, "\\\\", 2);
2155 else out = sdscatlen(out, n, 1);
2156 n++;
2157 }
2158
2159 sdsfree(tmp);
2160 return out;
2161 } else {
2162 assert(0);
2163 }
2164}
2165
2166static sds cliFormatReplyJson(sds out, redisReply *r, int mode) {
2167 unsigned int i;
2168
2169 switch (r->type) {
2170 case REDIS_REPLY_ERROR:
2171 out = sdscat(out,"error:");
2172 out = jsonStringOutput(out,r->str,strlen(r->str),mode);
2173 break;
2174 case REDIS_REPLY_STATUS:
2175 out = jsonStringOutput(out,r->str,r->len,mode);
2176 break;
2177 case REDIS_REPLY_INTEGER:
2178 out = sdscatprintf(out,"%lld",r->integer);
2179 break;
2180 case REDIS_REPLY_DOUBLE:
2181 out = sdscatprintf(out,"%s",r->str);
2182 break;
2183 case REDIS_REPLY_STRING:
2184 case REDIS_REPLY_VERB:
2185 out = jsonStringOutput(out,r->str,r->len,mode);
2186 break;
2187 case REDIS_REPLY_NIL:
2188 out = sdscat(out,"null");
2189 break;
2190 case REDIS_REPLY_BOOL:
2191 out = sdscat(out,r->integer ? "true" : "false");
2192 break;
2193 case REDIS_REPLY_ARRAY:
2194 case REDIS_REPLY_SET:
2195 case REDIS_REPLY_PUSH:
2196 out = sdscat(out,"[");
2197 for (i = 0; i < r->elements; i++ ) {
2198 out = cliFormatReplyJson(out,r->element[i],mode);
2199 if (i != r->elements-1) out = sdscat(out,",");
2200 }
2201 out = sdscat(out,"]");
2202 break;
2203 case REDIS_REPLY_MAP:
2204 out = sdscat(out,"{");
2205 for (i = 0; i < r->elements; i += 2) {
2206 redisReply *key = r->element[i];
2207 if (key->type == REDIS_REPLY_ERROR ||
2208 key->type == REDIS_REPLY_STATUS ||
2209 key->type == REDIS_REPLY_STRING ||
2210 key->type == REDIS_REPLY_VERB)
2211 {
2212 out = cliFormatReplyJson(out,key,mode);
2213 } else {
2214 /* According to JSON spec, JSON map keys must be strings,
2215 * and in RESP3, they can be other types.
2216 * The first one(cliFormatReplyJson) is to convert non string type to string
2217 * The Second one(escapeJsonString) is to escape the converted string */
2218 sds keystr = cliFormatReplyJson(sdsempty(),key,mode);
2219 if (keystr[0] == '"') out = sdscatsds(out,keystr);
2220 else out = sdscatfmt(out,"\"%S\"",keystr);
2221 sdsfree(keystr);
2222 }
2223 out = sdscat(out,":");
2224
2225 out = cliFormatReplyJson(out,r->element[i+1],mode);
2226 if (i != r->elements-2) out = sdscat(out,",");
2227 }
2228 out = sdscat(out,"}");
2229 break;
2230 default:
2231 fprintf(stderr,"Unknown reply type: %d\n", r->type);
2232 exit(1);
2233 }
2234 return out;
2235}
2236
2237/* Generate reply strings in various output modes */
2238static sds cliFormatReply(redisReply *reply, int mode, int verbatim) {
2239 sds out;
2240
2241 if (verbatim) {
2242 out = cliFormatReplyRaw(reply);
2243 } else if (mode == OUTPUT_STANDARD) {
2244 out = cliFormatReplyTTY(reply, "");
2245 } else if (mode == OUTPUT_RAW) {
2246 out = cliFormatReplyRaw(reply);
2247 out = sdscatsds(out, config.cmd_delim);
2248 } else if (mode == OUTPUT_CSV) {
2249 out = cliFormatReplyCSV(reply);
2250 out = sdscatlen(out, "\n", 1);
2251 } else if (mode == OUTPUT_JSON || mode == OUTPUT_QUOTED_JSON) {
2252 out = cliFormatReplyJson(sdsempty(), reply, mode);
2253 out = sdscatlen(out, "\n", 1);
2254 } else {
2255 fprintf(stderr, "Error: Unknown output encoding %d\n", mode);
2256 exit(1);
2257 }
2258
2259 return out;
2260}
2261
2262/* Output any spontaneous PUSH reply we receive */
2263static void cliPushHandler(void *privdata, void *reply) {
2264 UNUSED(privdata);
2265 sds out;
2266
2267 if (config.output == OUTPUT_STANDARD && isInvalidateReply(reply)) {
2268 out = cliFormatInvalidateTTY(reply);
2269 } else {
2270 out = cliFormatReply(reply, config.output, 0);
2271 }
2272
2273 fwrite(out, sdslen(out), 1, stdout);
2274
2275 freeReplyObject(reply);
2276 sdsfree(out);
2277}
2278
2279static int cliReadReply(int output_raw_strings) {
2280 void *_reply;
2281 redisReply *reply;
2282 sds out = NULL;
2283 int output = 1;
2284
2285 if (config.last_reply) {
2286 freeReplyObject(config.last_reply);
2287 config.last_reply = NULL;
2288 }
2289
2290 if (redisGetReply(context,&_reply) != REDIS_OK) {
2291 if (config.blocking_state_aborted) {
2292 config.blocking_state_aborted = 0;
2293 config.monitor_mode = 0;
2294 config.pubsub_mode = 0;
2295 return cliConnect(CC_FORCE);
2296 }
2297
2298 if (config.shutdown) {
2299 redisFree(context);
2300 context = NULL;
2301 return REDIS_OK;
2302 }
2303 if (config.interactive) {
2304 /* Filter cases where we should reconnect */
2305 if (context->err == REDIS_ERR_IO &&
2306 (errno == ECONNRESET || errno == EPIPE))
2307 return REDIS_ERR;
2308 if (context->err == REDIS_ERR_EOF)
2309 return REDIS_ERR;
2310 }
2311 cliPrintContextError();
2312 exit(1);
2313 return REDIS_ERR; /* avoid compiler warning */
2314 }
2315
2316 config.last_reply = reply = (redisReply*)_reply;
2317
2318 config.last_cmd_type = reply->type;
2319
2320 /* Check if we need to connect to a different node and reissue the
2321 * request. */
2322 if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
2323 (!strncmp(reply->str,"MOVED ",6) || !strncmp(reply->str,"ASK ",4)))
2324 {
2325 char *p = reply->str, *s;
2326 int slot;
2327
2328 output = 0;
2329 /* Comments show the position of the pointer as:
2330 *
2331 * [S] for pointer 's'
2332 * [P] for pointer 'p'
2333 */
2334 s = strchr(p,' '); /* MOVED[S]3999 127.0.0.1:6381 */
2335 p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */
2336 *p = '\0';
2337 slot = atoi(s+1);
2338 s = strrchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */
2339 *s = '\0';
2340 if (p+1 != s) {
2341 /* Host might be empty, like 'MOVED 3999 :6381', if endpoint type is unknown. Only update the
2342 * host if it's non-empty. */
2343 sdsfree(config.conn_info.hostip);
2344 config.conn_info.hostip = sdsnew(p+1);
2345 }
2346 config.conn_info.hostport = atoi(s+1);
2347 if (config.interactive)
2348 printf("-> Redirected to slot [%d] located at %s:%d\n",
2349 slot, config.conn_info.hostip, config.conn_info.hostport);
2350 config.cluster_reissue_command = 1;
2351 if (!strncmp(reply->str,"ASK ",4)) {
2352 config.cluster_send_asking = 1;
2353 }
2354 cliRefreshPrompt();
2355 } else if (!config.interactive && config.set_errcode &&
2356 reply->type == REDIS_REPLY_ERROR)
2357 {
2358 fprintf(stderr,"%s\n",reply->str);
2359 exit(1);
2360 return REDIS_ERR; /* avoid compiler warning */
2361 }
2362
2363 if (output) {
2364 out = cliFormatReply(reply, config.output, output_raw_strings);
2365 fwrite(out,sdslen(out),1,stdout);
2366 fflush(stdout);
2367 sdsfree(out);
2368 }
2369 return REDIS_OK;
2370}
2371
2372/* Simultaneously wait for pubsub messages from redis and input on stdin. */
2373static void cliWaitForMessagesOrStdin(void) {
2374 int show_info = config.output != OUTPUT_RAW && (isatty(STDOUT_FILENO) ||
2375 getenv("FAKETTY"));
2376 int use_color = show_info && isColorTerm();
2377 cliPressAnyKeyTTY();
2378 while (config.pubsub_mode) {
2379 /* First check if there are any buffered replies. */
2380 redisReply *reply;
2381 do {
2382 if (redisGetReplyFromReader(context, (void **)&reply) != REDIS_OK) {
2383 cliPrintContextError();
2384 exit(1);
2385 }
2386 if (reply) {
2387 sds out = cliFormatReply(reply, config.output, 0);
2388 fwrite(out,sdslen(out),1,stdout);
2389 fflush(stdout);
2390 sdsfree(out);
2391 }
2392 } while(reply);
2393
2394 /* Wait for input, either on the Redis socket or on stdin. */
2395 struct timeval tv;
2396 fd_set readfds;
2397 FD_ZERO(&readfds);
2398 FD_SET(context->fd, &readfds);
2399 FD_SET(STDIN_FILENO, &readfds);
2400 tv.tv_sec = 5;
2401 tv.tv_usec = 0;
2402 if (show_info) {
2403 if (use_color) printf("\033[1;90m"); /* Bold, bright color. */
2404 printf("Reading messages... (press Ctrl-C to quit or any key to type command)\r");
2405 if (use_color) printf("\033[0m"); /* Reset color. */
2406 fflush(stdout);
2407 }
2408 select(context->fd + 1, &readfds, NULL, NULL, &tv);
2409 if (show_info) {
2410 printf("\033[K"); /* Erase current line */
2411 fflush(stdout);
2412 }
2413 if (config.blocking_state_aborted) {
2414 /* Ctrl-C pressed */
2415 config.blocking_state_aborted = 0;
2416 config.pubsub_mode = 0;
2417 if (cliConnect(CC_FORCE) != REDIS_OK) {
2418 cliPrintContextError();
2419 exit(1);
2420 }
2421 break;
2422 } else if (FD_ISSET(context->fd, &readfds)) {
2423 /* Message from Redis */
2424 if (cliReadReply(0) != REDIS_OK) {
2425 cliPrintContextError();
2426 exit(1);
2427 }
2428 fflush(stdout);
2429 } else if (FD_ISSET(STDIN_FILENO, &readfds)) {
2430 /* Any key pressed */
2431 break;
2432 }
2433 }
2434 cliRestoreTTY();
2435}
2436
2437static int cliSendCommand(int argc, char **argv, long repeat) {
2438 char *command = argv[0];
2439 size_t *argvlen;
2440 int j, output_raw;
2441
2442 if (context == NULL) return REDIS_ERR;
2443
2444 output_raw = 0;
2445 if (!strcasecmp(command,"info") ||
2446 !strcasecmp(command,"lolwut") ||
2447 (argc >= 2 && !strcasecmp(command,"debug") &&
2448 !strcasecmp(argv[1],"htstats")) ||
2449 (argc >= 2 && !strcasecmp(command,"debug") &&
2450 !strcasecmp(argv[1],"htstats-key")) ||
2451 (argc >= 2 && !strcasecmp(command,"debug") &&
2452 !strcasecmp(argv[1],"client-eviction")) ||
2453 (argc >= 2 && !strcasecmp(command,"memory") &&
2454 (!strcasecmp(argv[1],"malloc-stats") ||
2455 !strcasecmp(argv[1],"doctor"))) ||
2456 (argc == 2 && !strcasecmp(command,"cluster") &&
2457 (!strcasecmp(argv[1],"nodes") ||
2458 !strcasecmp(argv[1],"info"))) ||
2459 (argc >= 2 && !strcasecmp(command,"client") &&
2460 (!strcasecmp(argv[1],"list") ||
2461 !strcasecmp(argv[1],"info"))) ||
2462 (argc == 3 && !strcasecmp(command,"latency") &&
2463 !strcasecmp(argv[1],"graph")) ||
2464 (argc == 2 && !strcasecmp(command,"latency") &&
2465 !strcasecmp(argv[1],"doctor")) ||
2466 /* Format PROXY INFO command for Redis Cluster Proxy:
2467 * https://github.com/artix75/redis-cluster-proxy */
2468 (argc >= 2 && !strcasecmp(command,"proxy") &&
2469 !strcasecmp(argv[1],"info")))
2470 {
2471 output_raw = 1;
2472 }
2473
2474 if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
2475 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
2476 int is_subscribe = (!strcasecmp(command, "subscribe") ||
2477 !strcasecmp(command, "psubscribe") ||
2478 !strcasecmp(command, "ssubscribe"));
2479 int is_unsubscribe = (!strcasecmp(command, "unsubscribe") ||
2480 !strcasecmp(command, "punsubscribe") ||
2481 !strcasecmp(command, "sunsubscribe"));
2482 if (!strcasecmp(command,"sync") ||
2483 !strcasecmp(command,"psync")) config.slave_mode = 1;
2484
2485 /* When the user manually calls SCRIPT DEBUG, setup the activation of
2486 * debugging mode on the next eval if needed. */
2487 if (argc == 3 && !strcasecmp(argv[0],"script") &&
2488 !strcasecmp(argv[1],"debug"))
2489 {
2490 if (!strcasecmp(argv[2],"yes") || !strcasecmp(argv[2],"sync")) {
2491 config.enable_ldb_on_eval = 1;
2492 } else {
2493 config.enable_ldb_on_eval = 0;
2494 }
2495 }
2496
2497 /* Actually activate LDB on EVAL if needed. */
2498 if (!strcasecmp(command,"eval") && config.enable_ldb_on_eval) {
2499 config.eval_ldb = 1;
2500 config.output = OUTPUT_RAW;
2501 }
2502
2503 /* Setup argument length */
2504 argvlen = zmalloc(argc*sizeof(size_t));
2505 for (j = 0; j < argc; j++)
2506 argvlen[j] = sdslen(argv[j]);
2507
2508 /* Negative repeat is allowed and causes infinite loop,
2509 works well with the interval option. */
2510 while(repeat < 0 || repeat-- > 0) {
2511 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
2512
2513 if (config.monitor_mode) {
2514 do {
2515 if (cliReadReply(output_raw) != REDIS_OK) {
2516 cliPrintContextError();
2517 exit(1);
2518 }
2519 fflush(stdout);
2520
2521 /* This happens when the MONITOR command returns an error. */
2522 if (config.last_cmd_type == REDIS_REPLY_ERROR)
2523 config.monitor_mode = 0;
2524 } while(config.monitor_mode);
2525 zfree(argvlen);
2526 return REDIS_OK;
2527 }
2528
2529 int num_expected_pubsub_push = 0;
2530 if (is_subscribe || is_unsubscribe) {
2531 /* When a push callback is set, redisGetReply (hiredis) loops until
2532 * an in-band message is received, but these commands are confirmed
2533 * using push replies only. There is one push reply per channel if
2534 * channels are specified, otherwise at least one. */
2535 num_expected_pubsub_push = argc > 1 ? argc - 1 : 1;
2536 /* Unset our default PUSH handler so this works in RESP2/RESP3 */
2537 redisSetPushCallback(context, NULL);
2538 }
2539
2540 if (config.slave_mode) {
2541 printf("Entering replica output mode... (press Ctrl-C to quit)\n");
2542 slaveMode(0);
2543 config.slave_mode = 0;
2544 zfree(argvlen);
2545 return REDIS_ERR; /* Error = slaveMode lost connection to master */
2546 }
2547
2548 /* Read response, possibly skipping pubsub/push messages. */
2549 while (1) {
2550 if (cliReadReply(output_raw) != REDIS_OK) {
2551 zfree(argvlen);
2552 return REDIS_ERR;
2553 }
2554 fflush(stdout);
2555 if (config.pubsub_mode || num_expected_pubsub_push > 0) {
2556 if (isPubsubPush(config.last_reply)) {
2557 if (num_expected_pubsub_push > 0 &&
2558 !strcasecmp(config.last_reply->element[0]->str, command))
2559 {
2560 /* This pushed message confirms the
2561 * [p|s][un]subscribe command. */
2562 if (is_subscribe && !config.pubsub_mode) {
2563 config.pubsub_mode = 1;
2564 cliRefreshPrompt();
2565 }
2566 if (--num_expected_pubsub_push > 0) {
2567 continue; /* We need more of these. */
2568 }
2569 } else {
2570 continue; /* Skip this pubsub message. */
2571 }
2572 } else if (config.last_reply->type == REDIS_REPLY_PUSH) {
2573 continue; /* Skip other push message. */
2574 }
2575 }
2576
2577 /* Store database number when SELECT was successfully executed. */
2578 if (!strcasecmp(command,"select") && argc == 2 &&
2579 config.last_cmd_type != REDIS_REPLY_ERROR)
2580 {
2581 config.conn_info.input_dbnum = config.dbnum = atoi(argv[1]);
2582 cliRefreshPrompt();
2583 } else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3)) {
2584 cliSelect();
2585 } else if (!strcasecmp(command,"multi") && argc == 1 &&
2586 config.last_cmd_type != REDIS_REPLY_ERROR)
2587 {
2588 config.in_multi = 1;
2589 config.pre_multi_dbnum = config.dbnum;
2590 cliRefreshPrompt();
2591 } else if (!strcasecmp(command,"exec") && argc == 1 && config.in_multi) {
2592 config.in_multi = 0;
2593 if (config.last_cmd_type == REDIS_REPLY_ERROR ||
2594 config.last_cmd_type == REDIS_REPLY_NIL)
2595 {
2596 config.conn_info.input_dbnum = config.dbnum = config.pre_multi_dbnum;
2597 }
2598 cliRefreshPrompt();
2599 } else if (!strcasecmp(command,"discard") && argc == 1 &&
2600 config.last_cmd_type != REDIS_REPLY_ERROR)
2601 {
2602 config.in_multi = 0;
2603 config.conn_info.input_dbnum = config.dbnum = config.pre_multi_dbnum;
2604 cliRefreshPrompt();
2605 } else if (!strcasecmp(command,"reset") && argc == 1 &&
2606 config.last_cmd_type != REDIS_REPLY_ERROR) {
2607 config.in_multi = 0;
2608 config.dbnum = 0;
2609 config.conn_info.input_dbnum = 0;
2610 config.current_resp3 = 0;
2611 if (config.pubsub_mode && config.push_output) {
2612 redisSetPushCallback(context, cliPushHandler);
2613 }
2614 config.pubsub_mode = 0;
2615 cliRefreshPrompt();
2616 } else if (!strcasecmp(command,"hello")) {
2617 if (config.last_cmd_type == REDIS_REPLY_MAP) {
2618 config.current_resp3 = 1;
2619 } else if (config.last_cmd_type == REDIS_REPLY_ARRAY) {
2620 config.current_resp3 = 0;
2621 }
2622 } else if ((is_subscribe || is_unsubscribe) && !config.pubsub_mode) {
2623 /* We didn't enter pubsub mode. Restore push callback. */
2624 if (config.push_output)
2625 redisSetPushCallback(context, cliPushHandler);
2626 }
2627
2628 break;
2629 }
2630 if (config.cluster_reissue_command){
2631 /* If we need to reissue the command, break to prevent a
2632 further 'repeat' number of dud interactions */
2633 break;
2634 }
2635 if (config.interval) usleep(config.interval);
2636 fflush(stdout); /* Make it grep friendly */
2637 }
2638
2639 zfree(argvlen);
2640 return REDIS_OK;
2641}
2642
2643/* Send a command reconnecting the link if needed. */
2644static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ...) {
2645 redisReply *reply = NULL;
2646 int tries = 0;
2647 va_list ap;
2648
2649 assert(!c->err);
2650 while(reply == NULL) {
2651 while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
2652 printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
2653 printf("Reconnecting... %d\r", ++tries);
2654 fflush(stdout);
2655
2656 redisFree(c);
2657 c = redisConnectWrapper(config.conn_info.hostip, config.conn_info.hostport,
2658 config.connect_timeout);
2659 if (!c->err && config.tls) {
2660 const char *err = NULL;
2661 if (cliSecureConnection(c, config.sslconfig, &err) == REDIS_ERR && err) {
2662 fprintf(stderr, "TLS Error: %s\n", err);
2663 exit(1);
2664 }
2665 }
2666 usleep(1000000);
2667 }
2668
2669 va_start(ap,fmt);
2670 reply = redisvCommand(c,fmt,ap);
2671 va_end(ap);
2672
2673 if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
2674 fprintf(stderr, "Error: %s\n", c->errstr);
2675 exit(1);
2676 } else if (tries > 0) {
2677 printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
2678 }
2679 }
2680
2681 context = c;
2682 return reply;
2683}
2684
2685/*------------------------------------------------------------------------------
2686 * User interface
2687 *--------------------------------------------------------------------------- */
2688
2689static int parseOptions(int argc, char **argv) {
2690 int i;
2691
2692 for (i = 1; i < argc; i++) {
2693 int lastarg = i==argc-1;
2694
2695 if (!strcmp(argv[i],"-h") && !lastarg) {
2696 sdsfree(config.conn_info.hostip);
2697 config.conn_info.hostip = sdsnew(argv[++i]);
2698 } else if (!strcmp(argv[i],"-h") && lastarg) {
2699 usage(0);
2700 } else if (!strcmp(argv[i],"--help")) {
2701 usage(0);
2702 } else if (!strcmp(argv[i],"-x")) {
2703 config.stdin_lastarg = 1;
2704 } else if (!strcmp(argv[i], "-X") && !lastarg) {
2705 config.stdin_tag_arg = 1;
2706 config.stdin_tag_name = argv[++i];
2707 } else if (!strcmp(argv[i],"-p") && !lastarg) {
2708 config.conn_info.hostport = atoi(argv[++i]);
2709 if (config.conn_info.hostport < 0 || config.conn_info.hostport > 65535) {
2710 fprintf(stderr, "Invalid server port.\n");
2711 exit(1);
2712 }
2713 } else if (!strcmp(argv[i],"-t") && !lastarg) {
2714 char *eptr;
2715 double seconds = strtod(argv[++i], &eptr);
2716 if (eptr[0] != '\0' || isnan(seconds) || seconds < 0.0) {
2717 fprintf(stderr, "Invalid connection timeout for -t.\n");
2718 exit(1);
2719 }
2720 config.connect_timeout.tv_sec = (long long)seconds;
2721 config.connect_timeout.tv_usec = ((long long)(seconds * 1000000)) % 1000000;
2722 } else if (!strcmp(argv[i],"-s") && !lastarg) {
2723 config.hostsocket = argv[++i];
2724 } else if (!strcmp(argv[i],"-r") && !lastarg) {
2725 config.repeat = strtoll(argv[++i],NULL,10);
2726 } else if (!strcmp(argv[i],"-i") && !lastarg) {
2727 double seconds = atof(argv[++i]);
2728 config.interval = seconds*1000000;
2729 } else if (!strcmp(argv[i],"-n") && !lastarg) {
2730 config.conn_info.input_dbnum = atoi(argv[++i]);
2731 } else if (!strcmp(argv[i], "--no-auth-warning")) {
2732 config.no_auth_warning = 1;
2733 } else if (!strcmp(argv[i], "--askpass")) {
2734 config.askpass = 1;
2735 } else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
2736 && !lastarg)
2737 {
2738 config.conn_info.auth = sdsnew(argv[++i]);
2739 } else if (!strcmp(argv[i],"--user") && !lastarg) {
2740 config.conn_info.user = sdsnew(argv[++i]);
2741 } else if (!strcmp(argv[i],"-u") && !lastarg) {
2742 parseRedisUri(argv[++i],"redis-cli",&config.conn_info,&config.tls);
2743 if (config.conn_info.hostport < 0 || config.conn_info.hostport > 65535) {
2744 fprintf(stderr, "Invalid server port.\n");
2745 exit(1);
2746 }
2747 } else if (!strcmp(argv[i],"--raw")) {
2748 config.output = OUTPUT_RAW;
2749 } else if (!strcmp(argv[i],"--no-raw")) {
2750 config.output = OUTPUT_STANDARD;
2751 } else if (!strcmp(argv[i],"--quoted-input")) {
2752 config.quoted_input = 1;
2753 } else if (!strcmp(argv[i],"--csv")) {
2754 config.output = OUTPUT_CSV;
2755 } else if (!strcmp(argv[i],"--json")) {
2756 /* Not overwrite explicit value by -3 */
2757 if (config.resp3 == 0) {
2758 config.resp3 = 2;
2759 }
2760 config.output = OUTPUT_JSON;
2761 } else if (!strcmp(argv[i],"--quoted-json")) {
2762 /* Not overwrite explicit value by -3*/
2763 if (config.resp3 == 0) {
2764 config.resp3 = 2;
2765 }
2766 config.output = OUTPUT_QUOTED_JSON;
2767 } else if (!strcmp(argv[i],"--latency")) {
2768 config.latency_mode = 1;
2769 } else if (!strcmp(argv[i],"--latency-dist")) {
2770 config.latency_dist_mode = 1;
2771 } else if (!strcmp(argv[i],"--mono")) {
2772 spectrum_palette = spectrum_palette_mono;
2773 spectrum_palette_size = spectrum_palette_mono_size;
2774 } else if (!strcmp(argv[i],"--latency-history")) {
2775 config.latency_mode = 1;
2776 config.latency_history = 1;
2777 } else if (!strcmp(argv[i],"--vset-recall") && !lastarg) {
2778 config.vset_recall_mode = 1;
2779 config.vset_recall_key = sdsnew(argv[++i]);
2780 } else if (!strcmp(argv[i],"--vset-recall-ele") && !lastarg) {
2781 config.vset_recall_ele_count = strtoll(argv[++i],NULL,10);
2782 if (config.vset_recall_ele_count <= 0)
2783 config.vset_recall_ele_count = 1;
2784 } else if (!strcmp(argv[i],"--vset-recall-count") && !lastarg) {
2785 config.vset_recall_vsim_count = strtoll(argv[++i],NULL,10);
2786 if (config.vset_recall_vsim_count <= 0)
2787 config.vset_recall_vsim_count = 1;
2788 } else if (!strcmp(argv[i],"--vset-recall-ef") && !lastarg) {
2789 config.vset_recall_vsim_ef = strtoll(argv[++i],NULL,10);
2790 if (config.vset_recall_vsim_ef <= 0)
2791 config.vset_recall_vsim_ef = 1;
2792 } else if (!strcmp(argv[i],"--lru-test") && !lastarg) {
2793 config.lru_test_mode = 1;
2794 config.lru_test_sample_size = strtoll(argv[++i],NULL,10);
2795 } else if (!strcmp(argv[i],"--slave")) {
2796 config.slave_mode = 1;
2797 } else if (!strcmp(argv[i],"--replica")) {
2798 config.slave_mode = 1;
2799 } else if (!strcmp(argv[i],"--stat")) {
2800 config.stat_mode = 1;
2801 } else if (!strcmp(argv[i],"--scan")) {
2802 config.scan_mode = 1;
2803 } else if (!strcmp(argv[i],"--pattern") && !lastarg) {
2804 sdsfree(config.pattern);
2805 config.pattern = sdsnew(argv[++i]);
2806 } else if (!strcmp(argv[i],"--count") && !lastarg) {
2807 config.count = atoi(argv[++i]);
2808 } else if (!strcmp(argv[i],"--quoted-pattern") && !lastarg) {
2809 sdsfree(config.pattern);
2810 config.pattern = unquoteCString(argv[++i]);
2811 if (!config.pattern) {
2812 fprintf(stderr,"Invalid quoted string specified for --quoted-pattern.\n");
2813 exit(1);
2814 }
2815 } else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
2816 config.intrinsic_latency_mode = 1;
2817 config.intrinsic_latency_duration = atoi(argv[++i]);
2818 } else if (!strcmp(argv[i],"--rdb") && !lastarg) {
2819 config.getrdb_mode = 1;
2820 config.rdb_filename = argv[++i];
2821 } else if (!strcmp(argv[i],"--functions-rdb") && !lastarg) {
2822 config.get_functions_rdb_mode = 1;
2823 config.rdb_filename = argv[++i];
2824 } else if (!strcmp(argv[i],"--pipe")) {
2825 config.pipe_mode = 1;
2826 } else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) {
2827 config.pipe_timeout = atoi(argv[++i]);
2828 } else if (!strcmp(argv[i],"--bigkeys")) {
2829 config.bigkeys = 1;
2830 } else if (!strcmp(argv[i],"--memkeys")) {
2831 config.memkeys = 1;
2832 config.memkeys_samples = -1; /* use redis default */
2833 } else if (!strcmp(argv[i],"--memkeys-samples") && !lastarg) {
2834 char *endptr;
2835 config.memkeys = 1;
2836 config.keystats = 1;
2837 config.memkeys_samples = strtoll(argv[++i], &endptr, 10);
2838 if (*endptr) {
2839 fprintf(stderr, "--memkeys-samples conversion error.\n");
2840 exit(1);
2841 }
2842 if (config.memkeys_samples < 0) {
2843 fprintf(stderr, "--memkeys-samples value should be positive.\n");
2844 exit(1);
2845 }
2846 } else if (!strcmp(argv[i],"--hotkeys")) {
2847 config.hotkeys = 1;
2848 } else if (!strcmp(argv[i], "--keystats")) {
2849 config.keystats = 1;
2850 config.memkeys_samples = -1; /* use redis default */
2851 } else if (!strcmp(argv[i],"--keystats-samples") && !lastarg) {
2852 char *endptr;
2853 config.keystats = 1;
2854 config.memkeys_samples = strtoll(argv[++i], &endptr, 10);
2855 if (*endptr) {
2856 fprintf(stderr, "--keystats-samples conversion error.\n");
2857 exit(1);
2858 }
2859 if (config.memkeys_samples < 0) {
2860 fprintf(stderr, "--keystats-samples value should be positive.\n");
2861 exit(1);
2862 }
2863 } else if (!strcmp(argv[i],"--cursor") && !lastarg) {
2864 i++;
2865 char sign = *argv[i];
2866 char *endptr;
2867 config.cursor = strtoull(argv[i], &endptr, 10);
2868 if (*endptr) {
2869 fprintf(stderr, "--cursor conversion error.\n");
2870 exit(1);
2871 }
2872 if (sign == '-' && config.cursor != 0) {
2873 fprintf(stderr, "--cursor should be followed by a positive integer.\n");
2874 exit(1);
2875 }
2876 } else if (!strcmp(argv[i],"--top") && !lastarg) {
2877 i++;
2878 char sign = *argv[i];
2879 char *endptr;
2880 config.top_sizes_limit = strtoull(argv[i], &endptr, 10);
2881 if (*endptr) {
2882 fprintf(stderr, "--top conversion error.\n");
2883 exit(1);
2884 }
2885 if (sign == '-' && config.top_sizes_limit != 0) {
2886 fprintf(stderr, "--top should be followed by a positive integer.\n");
2887 exit(1);
2888 }
2889 } else if (!strcmp(argv[i],"--eval") && !lastarg) {
2890 config.eval = argv[++i];
2891 } else if (!strcmp(argv[i],"--ldb")) {
2892 config.eval_ldb = 1;
2893 config.output = OUTPUT_RAW;
2894 } else if (!strcmp(argv[i],"--ldb-sync-mode")) {
2895 config.eval_ldb = 1;
2896 config.eval_ldb_sync = 1;
2897 config.output = OUTPUT_RAW;
2898 } else if (!strcmp(argv[i],"-c")) {
2899 config.cluster_mode = 1;
2900 } else if (!strcmp(argv[i],"-d") && !lastarg) {
2901 sdsfree(config.mb_delim);
2902 config.mb_delim = sdsnew(argv[++i]);
2903 } else if (!strcmp(argv[i],"-D") && !lastarg) {
2904 sdsfree(config.cmd_delim);
2905 config.cmd_delim = sdsnew(argv[++i]);
2906 } else if (!strcmp(argv[i],"-e")) {
2907 config.set_errcode = 1;
2908 } else if (!strcmp(argv[i],"--verbose")) {
2909 config.verbose = 1;
2910 } else if (!strcmp(argv[i],"-4")) {
2911 config.prefer_ipv4 = 1;
2912 } else if (!strcmp(argv[i],"-6")) {
2913 config.prefer_ipv6 = 1;
2914 } else if (!strcmp(argv[i],"--cluster") && !lastarg) {
2915 if (CLUSTER_MANAGER_MODE()) usage(1);
2916 char *cmd = argv[++i];
2917 int j = i;
2918 while (j < argc && argv[j][0] != '-') j++;
2919 if (j > i) j--;
2920 int err = createClusterManagerCommand(cmd, j - i, argv + i + 1);
2921 if (err) exit(err);
2922 i = j;
2923 } else if (!strcmp(argv[i],"--cluster") && lastarg) {
2924 usage(1);
2925 } else if ((!strcmp(argv[i],"--cluster-only-masters"))) {
2926 config.cluster_manager_command.flags |=
2927 CLUSTER_MANAGER_CMD_FLAG_MASTERS_ONLY;
2928 } else if ((!strcmp(argv[i],"--cluster-only-replicas"))) {
2929 config.cluster_manager_command.flags |=
2930 CLUSTER_MANAGER_CMD_FLAG_SLAVES_ONLY;
2931 } else if (!strcmp(argv[i],"--cluster-replicas") && !lastarg) {
2932 config.cluster_manager_command.replicas = atoi(argv[++i]);
2933 } else if (!strcmp(argv[i],"--cluster-master-id") && !lastarg) {
2934 config.cluster_manager_command.master_id = argv[++i];
2935 } else if (!strcmp(argv[i],"--cluster-from") && !lastarg) {
2936 config.cluster_manager_command.from = argv[++i];
2937 } else if (!strcmp(argv[i],"--cluster-to") && !lastarg) {
2938 config.cluster_manager_command.to = argv[++i];
2939 } else if (!strcmp(argv[i],"--cluster-from-user") && !lastarg) {
2940 config.cluster_manager_command.from_user = argv[++i];
2941 } else if (!strcmp(argv[i],"--cluster-from-pass") && !lastarg) {
2942 config.cluster_manager_command.from_pass = argv[++i];
2943 } else if (!strcmp(argv[i], "--cluster-from-askpass")) {
2944 config.cluster_manager_command.from_askpass = 1;
2945 } else if (!strcmp(argv[i],"--cluster-weight") && !lastarg) {
2946 if (config.cluster_manager_command.weight != NULL) {
2947 fprintf(stderr, "WARNING: you cannot use --cluster-weight "
2948 "more than once.\n"
2949 "You can set more weights by adding them "
2950 "as a space-separated list, ie:\n"
2951 "--cluster-weight n1=w n2=w\n");
2952 exit(1);
2953 }
2954 int widx = i + 1;
2955 char **weight = argv + widx;
2956 int wargc = 0;
2957 for (; widx < argc; widx++) {
2958 if (strstr(argv[widx], "--") == argv[widx]) break;
2959 if (strchr(argv[widx], '=') == NULL) break;
2960 wargc++;
2961 }
2962 if (wargc > 0) {
2963 config.cluster_manager_command.weight = weight;
2964 config.cluster_manager_command.weight_argc = wargc;
2965 i += wargc;
2966 }
2967 } else if (!strcmp(argv[i],"--cluster-slots") && !lastarg) {
2968 config.cluster_manager_command.slots = atoi(argv[++i]);
2969 } else if (!strcmp(argv[i],"--cluster-timeout") && !lastarg) {
2970 config.cluster_manager_command.timeout = atoi(argv[++i]);
2971 } else if (!strcmp(argv[i],"--cluster-pipeline") && !lastarg) {
2972 config.cluster_manager_command.pipeline = atoi(argv[++i]);
2973 } else if (!strcmp(argv[i],"--cluster-threshold") && !lastarg) {
2974 config.cluster_manager_command.threshold = atof(argv[++i]);
2975 } else if (!strcmp(argv[i],"--cluster-yes")) {
2976 config.cluster_manager_command.flags |=
2977 CLUSTER_MANAGER_CMD_FLAG_YES;
2978 } else if (!strcmp(argv[i],"--cluster-simulate")) {
2979 config.cluster_manager_command.flags |=
2980 CLUSTER_MANAGER_CMD_FLAG_SIMULATE;
2981 } else if (!strcmp(argv[i],"--cluster-replace")) {
2982 config.cluster_manager_command.flags |=
2983 CLUSTER_MANAGER_CMD_FLAG_REPLACE;
2984 } else if (!strcmp(argv[i],"--cluster-copy")) {
2985 config.cluster_manager_command.flags |=
2986 CLUSTER_MANAGER_CMD_FLAG_COPY;
2987 } else if (!strcmp(argv[i],"--cluster-slave")) {
2988 config.cluster_manager_command.flags |=
2989 CLUSTER_MANAGER_CMD_FLAG_SLAVE;
2990 } else if (!strcmp(argv[i],"--cluster-use-empty-masters")) {
2991 config.cluster_manager_command.flags |=
2992 CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER;
2993 } else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) {
2994 config.cluster_manager_command.flags |=
2995 CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
2996 } else if (!strcmp(argv[i],"--cluster-fix-with-unreachable-masters")) {
2997 config.cluster_manager_command.flags |=
2998 CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
2999 } else if (!strcmp(argv[i],"--test_hint") && !lastarg) {
3000 config.test_hint = argv[++i];
3001 } else if (!strcmp(argv[i],"--test_hint_file") && !lastarg) {
3002 config.test_hint_file = argv[++i];
3003 } else if (!strcmp(argv[i], "--name") && !lastarg) {
3004 config.client_name = argv[++i];
3005#ifdef USE_OPENSSL
3006 } else if (!strcmp(argv[i],"--tls")) {
3007 config.tls = 1;
3008 } else if (!strcmp(argv[i],"--sni") && !lastarg) {
3009 config.sslconfig.sni = argv[++i];
3010 } else if (!strcmp(argv[i],"--cacertdir") && !lastarg) {
3011 config.sslconfig.cacertdir = argv[++i];
3012 } else if (!strcmp(argv[i],"--cacert") && !lastarg) {
3013 config.sslconfig.cacert = argv[++i];
3014 } else if (!strcmp(argv[i],"--cert") && !lastarg) {
3015 config.sslconfig.cert = argv[++i];
3016 } else if (!strcmp(argv[i],"--key") && !lastarg) {
3017 config.sslconfig.key = argv[++i];
3018 } else if (!strcmp(argv[i],"--tls-ciphers") && !lastarg) {
3019 config.sslconfig.ciphers = argv[++i];
3020 } else if (!strcmp(argv[i],"--insecure")) {
3021 config.sslconfig.skip_cert_verify = 1;
3022 #ifdef TLS1_3_VERSION
3023 } else if (!strcmp(argv[i],"--tls-ciphersuites") && !lastarg) {
3024 config.sslconfig.ciphersuites = argv[++i];
3025 #endif
3026#endif
3027 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
3028 sds version = cliVersion();
3029 printf("redis-cli %s\n", version);
3030 sdsfree(version);
3031 exit(0);
3032 } else if (!strcmp(argv[i],"-2")) {
3033 config.resp2 = 1;
3034 } else if (!strcmp(argv[i],"-3")) {
3035 config.resp3 = 1;
3036 } else if (!strcmp(argv[i],"--show-pushes") && !lastarg) {
3037 char *argval = argv[++i];
3038 if (!strncasecmp(argval, "n", 1)) {
3039 config.push_output = 0;
3040 } else if (!strncasecmp(argval, "y", 1)) {
3041 config.push_output = 1;
3042 } else {
3043 fprintf(stderr, "Unknown --show-pushes value '%s' "
3044 "(valid: '[y]es', '[n]o')\n", argval);
3045 }
3046 } else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
3047 if (config.cluster_manager_command.argc == 0) {
3048 int j = i + 1;
3049 while (j < argc && argv[j][0] != '-') j++;
3050 int cmd_argc = j - i;
3051 config.cluster_manager_command.argc = cmd_argc;
3052 config.cluster_manager_command.argv = argv + i;
3053 if (cmd_argc > 1) i = j - 1;
3054 }
3055 } else {
3056 if (argv[i][0] == '-') {
3057 fprintf(stderr,
3058 "Unrecognized option or bad number of args for: '%s'\n",
3059 argv[i]);
3060 exit(1);
3061 } else {
3062 /* Likely the command name, stop here. */
3063 break;
3064 }
3065 }
3066 }
3067
3068 if (config.hostsocket && config.cluster_mode) {
3069 fprintf(stderr,"Options -c and -s are mutually exclusive.\n");
3070 exit(1);
3071 }
3072
3073 if (config.resp2 && config.resp3 == 1) {
3074 fprintf(stderr,"Options -2 and -3 are mutually exclusive.\n");
3075 exit(1);
3076 }
3077
3078 /* --ldb requires --eval. */
3079 if (config.eval_ldb && config.eval == NULL) {
3080 fprintf(stderr,"Options --ldb and --ldb-sync-mode require --eval.\n");
3081 fprintf(stderr,"Try %s --help for more information.\n", argv[0]);
3082 exit(1);
3083 }
3084
3085 if (!config.no_auth_warning && config.conn_info.auth != NULL) {
3086 fputs("Warning: Using a password with '-a' or '-u' option on the command"
3087 " line interface may not be safe.\n", stderr);
3088 }
3089
3090 if (config.get_functions_rdb_mode && config.getrdb_mode) {
3091 fprintf(stderr,"Option --functions-rdb and --rdb are mutually exclusive.\n");
3092 exit(1);
3093 }
3094
3095 if (config.stdin_lastarg && config.stdin_tag_arg) {
3096 fprintf(stderr, "Options -x and -X are mutually exclusive.\n");
3097 exit(1);
3098 }
3099
3100 if (config.prefer_ipv4 && config.prefer_ipv6) {
3101 fprintf(stderr, "Options -4 and -6 are mutually exclusive.\n");
3102 exit(1);
3103 }
3104
3105 return i;
3106}
3107
3108static void parseEnv(void) {
3109 /* Set auth from env, but do not overwrite CLI arguments if passed */
3110 char *auth = getenv(REDIS_CLI_AUTH_ENV);
3111 if (auth != NULL && config.conn_info.auth == NULL) {
3112 config.conn_info.auth = auth;
3113 }
3114
3115 char *cluster_yes = getenv(REDIS_CLI_CLUSTER_YES_ENV);
3116 if (cluster_yes != NULL && !strcmp(cluster_yes, "1")) {
3117 config.cluster_manager_command.flags |= CLUSTER_MANAGER_CMD_FLAG_YES;
3118 }
3119}
3120
3121static void usage(int err) {
3122 sds version = cliVersion();
3123 FILE *target = err ? stderr: stdout;
3124 const char *tls_usage =
3125#ifdef USE_OPENSSL
3126" --tls Establish a secure TLS connection.\n"
3127" --sni <host> Server name indication for TLS.\n"
3128" --cacert <file> CA Certificate file to verify with.\n"
3129" --cacertdir <dir> Directory where trusted CA certificates are stored.\n"
3130" If neither cacert nor cacertdir are specified, the default\n"
3131" system-wide trusted root certs configuration will apply.\n"
3132" --insecure Allow insecure TLS connection by skipping cert validation.\n"
3133" --cert <file> Client certificate to authenticate with.\n"
3134" --key <file> Private key file to authenticate with.\n"
3135" --tls-ciphers <list> Sets the list of preferred ciphers (TLSv1.2 and below)\n"
3136" in order of preference from highest to lowest separated by colon (\":\").\n"
3137" See the ciphers(1ssl) manpage for more information about the syntax of this string.\n"
3138#ifdef TLS1_3_VERSION
3139" --tls-ciphersuites <list> Sets the list of preferred ciphersuites (TLSv1.3)\n"
3140" in order of preference from highest to lowest separated by colon (\":\").\n"
3141" See the ciphers(1ssl) manpage for more information about the syntax of this string,\n"
3142" and specifically for TLSv1.3 ciphersuites.\n"
3143#endif
3144#endif
3145"";
3146
3147 fprintf(target,
3148"redis-cli %s\n"
3149"\n"
3150"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
3151" -h <hostname> Server hostname (default: 127.0.0.1).\n"
3152" -p <port> Server port (default: 6379).\n"
3153" -t <timeout> Server connection timeout in seconds (decimals allowed).\n"
3154" Default timeout is 0, meaning no limit, depending on the OS.\n"
3155" -s <socket> Server socket (overrides hostname and port).\n"
3156" -a <password> Password to use when connecting to the server.\n"
3157" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
3158" variable to pass this password more safely\n"
3159" (if both are used, this argument takes precedence).\n"
3160" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
3161" --pass <password> Alias of -a for consistency with the new --user option.\n"
3162" --askpass Force user to input password with mask from STDIN.\n"
3163" If this argument is used, '-a' and " REDIS_CLI_AUTH_ENV "\n"
3164" environment variable will be ignored.\n"
3165" -u <uri> Server URI on format redis://user:password@host:port/dbnum\n"
3166" User, password and dbnum are optional. For authentication\n"
3167" without a username, use username 'default'. For TLS, use\n"
3168" the scheme 'rediss'.\n"
3169" -r <repeat> Execute specified command N times.\n"
3170" -i <interval> When -r is used, waits <interval> seconds per command.\n"
3171" It is possible to specify sub-second times like -i 0.1.\n"
3172" This interval is also used in --scan and --stat per cycle.\n"
3173" and in --bigkeys, --memkeys, --keystats, and --hotkeys per 100 cycles.\n"
3174" -n <db> Database number.\n"
3175" --name <name> Set the client name.\n"
3176" -2 Start session in RESP2 protocol mode.\n"
3177" -3 Start session in RESP3 protocol mode.\n"
3178" -x Read last argument from STDIN (see example below).\n"
3179" -X Read <tag> argument from STDIN (see example below).\n"
3180" -d <delimiter> Delimiter between response bulks for raw formatting (default: \\n).\n"
3181" -D <delimiter> Delimiter between responses for raw formatting (default: \\n).\n"
3182" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
3183" -e Return exit error code when command execution fails.\n"
3184" -4 Prefer IPv4 over IPv6 on DNS lookup.\n"
3185" -6 Prefer IPv6 over IPv4 on DNS lookup.\n"
3186"%s"
3187" --raw Use raw formatting for replies (default when STDOUT is\n"
3188" not a tty).\n"
3189" --no-raw Force formatted output even when STDOUT is not a tty.\n"
3190" --quoted-input Force input to be handled as quoted strings.\n"
3191" --csv Output in CSV format.\n"
3192" --json Output in JSON format (default RESP3, use -2 if you want to use with RESP2).\n"
3193" --quoted-json Same as --json, but produce ASCII-safe quoted strings, not Unicode.\n"
3194" --show-pushes <yn> Whether to print RESP3 PUSH messages. Enabled by default when\n"
3195" STDOUT is a tty but can be overridden with --show-pushes no.\n"
3196" --stat Print rolling stats about server: mem, clients, ...\n",
3197version,tls_usage);
3198
3199 fprintf(target,
3200" --latency Enter a special mode continuously sampling latency.\n"
3201" If you use this mode in an interactive session it runs\n"
3202" forever displaying real-time stats. Otherwise if --raw or\n"
3203" --csv is specified, or if you redirect the output to a non\n"
3204" TTY, it samples the latency for 1 second (you can use\n"
3205" -i to change the interval), then produces a single output\n"
3206" and exits.\n"
3207" --latency-history Like --latency but tracking latency changes over time.\n"
3208" Default time interval is 15 sec. Change it using -i.\n"
3209" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
3210" Default time interval is 1 sec. Change it using -i.\n"
3211" --vset-recall <key> Enable VSIM recall test mode for the specified key\n"
3212" (that must be a vector set). Random vectors are created\n"
3213" mixing components from other elements. A VSIM is then\n"
3214" executed and checked against ground truth.\n"
3215" --vset-recall-count <count> How many top elements to fetch per query.\n"
3216" --vset-recall-ef <ef> HSNW EF (search effort) to use. Default 500.\n"
3217" --vset-recall-ele <count> Number of elements used to compose query vectors\n"
3218" Default 1.\n"
3219" --lru-test <keys> Simulate a cache workload with an 80-20 distribution.\n"
3220" --replica Simulate a replica showing commands received from the master.\n"
3221" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
3222" Use filename of \"-\" to write to stdout.\n"
3223" --functions-rdb <filename> Like --rdb but only get the functions (not the keys)\n"
3224" when getting the RDB dump file.\n"
3225" --pipe Transfer raw Redis protocol from stdin to server.\n"
3226" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
3227" no reply is received within <n> seconds.\n"
3228" Default timeout: %d. Use 0 to wait forever.\n",
3229 REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
3230 fprintf(target,
3231" --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n"
3232" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
3233" --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n"
3234" And define number of key elements to sample\n"
3235" --keystats Sample Redis keys looking for keys memory size and length (combine bigkeys and memkeys).\n"
3236" --keystats-samples <n> Sample Redis keys looking for keys memory size and length.\n"
3237" And define number of key elements to sample (only for memory usage).\n"
3238" --cursor <n> Start the scan at the cursor <n> (usually after a Ctrl-C).\n"
3239" Optionally used with --keystats and --keystats-samples.\n"
3240" --top <n> To display <n> top key sizes (default: 10).\n"
3241" Optionally used with --keystats and --keystats-samples.\n"
3242" --hotkeys Sample Redis keys looking for hot keys.\n"
3243" only works when maxmemory-policy is *lfu.\n"
3244" --scan List all keys using the SCAN command.\n"
3245" --pattern <pat> Keys pattern when using the --scan, --bigkeys, --memkeys,\n"
3246" --keystats or --hotkeys options (default: *).\n"
3247" --count <count> Count option when using the --scan, --bigkeys, --memkeys,\n"
3248" --keystats or --hotkeys (default: 10).\n"
3249" --quoted-pattern <pat> Same as --pattern, but the specified string can be\n"
3250" quoted, in order to pass an otherwise non binary-safe string.\n"
3251" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
3252" The test will run for the specified amount of seconds.\n"
3253" --eval <file> Send an EVAL command using the Lua script at <file>.\n"
3254" --ldb Used with --eval enable the Redis Lua debugger.\n"
3255" --ldb-sync-mode Like --ldb but uses the synchronous Lua debugger, in\n"
3256" this mode the server is blocked and script changes are\n"
3257" not rolled back from the server memory.\n"
3258" --cluster <command> [args...] [opts...]\n"
3259" Cluster Manager command and arguments (see below).\n"
3260" --verbose Verbose mode.\n"
3261" --no-auth-warning Don't show warning message when using password on command\n"
3262" line interface.\n"
3263" --help Output this help and exit.\n"
3264" --version Output version and exit.\n"
3265"\n");
3266 /* Using another fprintf call to avoid -Woverlength-strings compile warning */
3267 fprintf(target,
3268"Cluster Manager Commands:\n"
3269" Use --cluster help to list all available cluster manager commands.\n"
3270"\n"
3271"Examples:\n"
3272" redis-cli -u redis://default:PASSWORD@localhost:6379/0\n"
3273" cat /etc/passwd | redis-cli -x set mypasswd\n"
3274" redis-cli -D \"\" --raw dump key > key.dump && redis-cli -X dump_tag restore key2 0 dump_tag replace < key.dump\n"
3275" redis-cli -r 100 lpush mylist x\n"
3276" redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
3277" redis-cli --quoted-input set '\"null-\\x00-separated\"' value\n"
3278" redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
3279" redis-cli --scan --pattern '*:12345*'\n"
3280" redis-cli --scan --pattern '*:12345*' --count 100\n"
3281"\n"
3282" (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
3283"\n"
3284"When no command is given, redis-cli starts in interactive mode.\n"
3285"Type \"help\" in interactive mode for information on available commands\n"
3286"and settings.\n"
3287"\n");
3288 sdsfree(version);
3289 exit(err);
3290}
3291
3292static int confirmWithYes(char *msg, int ignore_force) {
3293 /* if --cluster-yes option is set and ignore_force is false,
3294 * do not prompt for an answer */
3295 if (!ignore_force &&
3296 (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_YES)) {
3297 return 1;
3298 }
3299
3300 printf("%s (type 'yes' to accept): ", msg);
3301 fflush(stdout);
3302 char buf[4];
3303 int nread = read(fileno(stdin),buf,4);
3304 buf[3] = '\0';
3305 return (nread != 0 && !strcmp("yes", buf));
3306}
3307
3308static int issueCommandRepeat(int argc, char **argv, long repeat) {
3309 /* In Lua debugging mode, we want to pass the "help" to Redis to get
3310 * it's own HELP message, rather than handle it by the CLI, see ldbRepl.
3311 *
3312 * For the normal Redis HELP, we can process it without a connection. */
3313 if (!config.eval_ldb &&
3314 (!strcasecmp(argv[0],"help") || !strcasecmp(argv[0],"?")))
3315 {
3316 cliOutputHelp(--argc, ++argv);
3317 return REDIS_OK;
3318 }
3319
3320 while (1) {
3321 if (config.cluster_reissue_command || context == NULL ||
3322 context->err == REDIS_ERR_IO || context->err == REDIS_ERR_EOF)
3323 {
3324 if (cliConnect(CC_FORCE) != REDIS_OK) {
3325 cliPrintContextError();
3326 config.cluster_reissue_command = 0;
3327 return REDIS_ERR;
3328 }
3329 /* Reset dbnum after reconnecting so we can re-select the previous db in cliSelect(). */
3330 config.dbnum = 0;
3331 cliSelect();
3332 }
3333 config.cluster_reissue_command = 0;
3334 if (config.cluster_send_asking) {
3335 if (cliSendAsking() != REDIS_OK) {
3336 cliPrintContextError();
3337 return REDIS_ERR;
3338 }
3339 }
3340 if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
3341 cliPrintContextError();
3342 redisFree(context);
3343 context = NULL;
3344 return REDIS_ERR;
3345 }
3346
3347 /* Issue the command again if we got redirected in cluster mode */
3348 if (config.cluster_mode && config.cluster_reissue_command) {
3349 continue;
3350 }
3351 break;
3352 }
3353 return REDIS_OK;
3354}
3355
3356static int issueCommand(int argc, char **argv) {
3357 return issueCommandRepeat(argc, argv, config.repeat);
3358}
3359
3360/* Split the user provided command into multiple SDS arguments.
3361 * This function normally uses sdssplitargs() from sds.c which is able
3362 * to understand "quoted strings", escapes and so forth. However when
3363 * we are in Lua debugging mode and the "eval" command is used, we want
3364 * the remaining Lua script (after "e " or "eval ") to be passed verbatim
3365 * as a single big argument. */
3366static sds *cliSplitArgs(char *line, int *argc) {
3367 if (config.eval_ldb && (strstr(line,"eval ") == line ||
3368 strstr(line,"e ") == line))
3369 {
3370 sds *argv = sds_malloc(sizeof(sds)*2);
3371 *argc = 2;
3372 int len = strlen(line);
3373 int elen = line[1] == ' ' ? 2 : 5; /* "e " or "eval "? */
3374 argv[0] = sdsnewlen(line,elen-1);
3375 argv[1] = sdsnewlen(line+elen,len-elen);
3376 return argv;
3377 } else {
3378 return sdssplitargs(line,argc);
3379 }
3380}
3381
3382/* Set the CLI preferences. This function is invoked when an interactive
3383 * ":command" is called, or when reading ~/.redisclirc file, in order to
3384 * set user preferences. */
3385void cliSetPreferences(char **argv, int argc, int interactive) {
3386 if (!strcasecmp(argv[0],":set") && argc >= 2) {
3387 if (!strcasecmp(argv[1],"hints")) pref.hints = 1;
3388 else if (!strcasecmp(argv[1],"nohints")) pref.hints = 0;
3389 else {
3390 printf("%sunknown redis-cli preference '%s'\n",
3391 interactive ? "" : ".redisclirc: ",
3392 argv[1]);
3393 }
3394 } else {
3395 printf("%sunknown redis-cli internal command '%s'\n",
3396 interactive ? "" : ".redisclirc: ",
3397 argv[0]);
3398 }
3399}
3400
3401/* Load the ~/.redisclirc file if any. */
3402void cliLoadPreferences(void) {
3403 sds rcfile = getDotfilePath(REDIS_CLI_RCFILE_ENV,REDIS_CLI_RCFILE_DEFAULT);
3404 if (rcfile == NULL) return;
3405 FILE *fp = fopen(rcfile,"r");
3406 char buf[1024];
3407
3408 if (fp) {
3409 while(fgets(buf,sizeof(buf),fp) != NULL) {
3410 sds *argv;
3411 int argc;
3412
3413 argv = sdssplitargs(buf,&argc);
3414 if (argc > 0) cliSetPreferences(argv,argc,0);
3415 sdsfreesplitres(argv,argc);
3416 }
3417 fclose(fp);
3418 }
3419 sdsfree(rcfile);
3420}
3421
3422/* Some commands can include sensitive information and shouldn't be put in the
3423 * history file. Currently these commands are include:
3424 * - AUTH
3425 * - ACL DELUSER, ACL SETUSER, ACL GETUSER
3426 * - CONFIG SET masterauth/masteruser/tls-key-file-pass/tls-client-key-file-pass/requirepass
3427 * - HELLO with [AUTH username password]
3428 * - MIGRATE with [AUTH password] or [AUTH2 username password]
3429 * - SENTINEL CONFIG SET sentinel-pass password, SENTINEL CONFIG SET sentinel-user username
3430 * - SENTINEL SET <mastername> auth-pass password, SENTINEL SET <mastername> auth-user username */
3431static int isSensitiveCommand(int argc, char **argv) {
3432 if (!strcasecmp(argv[0],"auth")) {
3433 return 1;
3434 } else if (argc > 1 &&
3435 !strcasecmp(argv[0],"acl") && (
3436 !strcasecmp(argv[1],"deluser") ||
3437 !strcasecmp(argv[1],"setuser") ||
3438 !strcasecmp(argv[1],"getuser")))
3439 {
3440 return 1;
3441 } else if (argc > 2 &&
3442 !strcasecmp(argv[0],"config") &&
3443 !strcasecmp(argv[1],"set")) {
3444 for (int j = 2; j < argc; j = j+2) {
3445 if (!strcasecmp(argv[j],"masterauth") ||
3446 !strcasecmp(argv[j],"masteruser") ||
3447 !strcasecmp(argv[j],"tls-key-file-pass") ||
3448 !strcasecmp(argv[j],"tls-client-key-file-pass") ||
3449 !strcasecmp(argv[j],"requirepass")) {
3450 return 1;
3451 }
3452 }
3453 return 0;
3454 /* HELLO [protover [AUTH username password] [SETNAME clientname]] */
3455 } else if (argc > 4 && !strcasecmp(argv[0],"hello")) {
3456 for (int j = 2; j < argc; j++) {
3457 int moreargs = argc - 1 - j;
3458 if (!strcasecmp(argv[j],"AUTH") && moreargs >= 2) {
3459 return 1;
3460 } else if (!strcasecmp(argv[j],"SETNAME") && moreargs) {
3461 j++;
3462 } else {
3463 return 0;
3464 }
3465 }
3466 /* MIGRATE host port key|"" destination-db timeout [COPY] [REPLACE]
3467 * [AUTH password] [AUTH2 username password] [KEYS key [key ...]] */
3468 } else if (argc > 7 && !strcasecmp(argv[0], "migrate")) {
3469 for (int j = 6; j < argc; j++) {
3470 int moreargs = argc - 1 - j;
3471 if (!strcasecmp(argv[j],"auth") && moreargs) {
3472 return 1;
3473 } else if (!strcasecmp(argv[j],"auth2") && moreargs >= 2) {
3474 return 1;
3475 } else if (!strcasecmp(argv[j],"keys") && moreargs) {
3476 return 0;
3477 }
3478 }
3479 } else if (argc > 4 && !strcasecmp(argv[0], "sentinel")) {
3480 /* SENTINEL CONFIG SET sentinel-pass password
3481 * SENTINEL CONFIG SET sentinel-user username */
3482 if (!strcasecmp(argv[1], "config") &&
3483 !strcasecmp(argv[2], "set") &&
3484 (!strcasecmp(argv[3], "sentinel-pass") ||
3485 !strcasecmp(argv[3], "sentinel-user")))
3486 {
3487 return 1;
3488 }
3489 /* SENTINEL SET <mastername> auth-pass password
3490 * SENTINEL SET <mastername> auth-user username */
3491 if (!strcasecmp(argv[1], "set") &&
3492 (!strcasecmp(argv[3], "auth-pass") ||
3493 !strcasecmp(argv[3], "auth-user")))
3494 {
3495 return 1;
3496 }
3497 }
3498 return 0;
3499}
3500
3501static void repl(void) {
3502 sds historyfile = NULL;
3503 int history = 0;
3504 char *line;
3505 int argc;
3506 sds *argv;
3507
3508 /* There is no need to initialize redis HELP when we are in lua debugger mode.
3509 * It has its own HELP and commands (COMMAND or COMMAND DOCS will fail and got nothing).
3510 * We will initialize the redis HELP after the Lua debugging session ended.*/
3511 if ((!config.eval_ldb) && isatty(fileno(stdin))) {
3512 /* Initialize the help using the results of the COMMAND command. */
3513 cliInitHelp();
3514 }
3515
3516 config.interactive = 1;
3517 linenoiseSetMultiLine(1);
3518 linenoiseSetCompletionCallback(completionCallback);
3519 linenoiseSetHintsCallback(hintsCallback);
3520 linenoiseSetFreeHintsCallback(freeHintsCallback);
3521
3522 /* Only use history and load the rc file when stdin is a tty. */
3523 if (getenv("FAKETTY_WITH_PROMPT") != NULL || isatty(fileno(stdin))) {
3524 historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV,REDIS_CLI_HISTFILE_DEFAULT);
3525 //keep in-memory history always regardless if history file can be determined
3526 history = 1;
3527 if (historyfile != NULL) {
3528 linenoiseHistoryLoad(historyfile);
3529 }
3530 cliLoadPreferences();
3531 }
3532
3533 cliRefreshPrompt();
3534 while(1) {
3535 line = linenoise(context ? config.prompt : "not connected> ");
3536 if (line == NULL) {
3537 /* ^C, ^D or similar. */
3538 if (config.pubsub_mode) {
3539 config.pubsub_mode = 0;
3540 if (cliConnect(CC_FORCE) == REDIS_OK)
3541 continue;
3542 }
3543 break;
3544 } else if (line[0] != '\0') {
3545 long repeat = 1;
3546 int skipargs = 0;
3547 char *endptr = NULL;
3548
3549 argv = cliSplitArgs(line,&argc);
3550 if (argv == NULL) {
3551 printf("Invalid argument(s)\n");
3552 fflush(stdout);
3553 if (history) linenoiseHistoryAdd(line, 0);
3554 if (historyfile) linenoiseHistorySave(historyfile);
3555 linenoiseFree(line);
3556 continue;
3557 } else if (argc == 0) {
3558 sdsfreesplitres(argv,argc);
3559 linenoiseFree(line);
3560 continue;
3561 }
3562
3563 /* check if we have a repeat command option and
3564 * need to skip the first arg */
3565 errno = 0;
3566 repeat = strtol(argv[0], &endptr, 10);
3567 if (argc > 1 && *endptr == '\0') {
3568 if (errno == ERANGE || errno == EINVAL || repeat <= 0) {
3569 fputs("Invalid redis-cli repeat command option value.\n", stdout);
3570 sdsfreesplitres(argv, argc);
3571 linenoiseFree(line);
3572 continue;
3573 }
3574 skipargs = 1;
3575 } else {
3576 repeat = 1;
3577 }
3578
3579 /* Always keep in-memory history. But for commands with sensitive information,
3580 * avoid writing them to the history file. */
3581 int is_sensitive = isSensitiveCommand(argc - skipargs, argv + skipargs);
3582 if (history) linenoiseHistoryAdd(line, is_sensitive);
3583 if (!is_sensitive && historyfile) linenoiseHistorySave(historyfile);
3584
3585 if (strcasecmp(argv[0],"quit") == 0 ||
3586 strcasecmp(argv[0],"exit") == 0)
3587 {
3588 exit(0);
3589 } else if (argv[0][0] == ':') {
3590 cliSetPreferences(argv,argc,1);
3591 sdsfreesplitres(argv,argc);
3592 linenoiseFree(line);
3593 continue;
3594 } else if (strcasecmp(argv[0],"restart") == 0) {
3595 if (config.eval) {
3596 config.eval_ldb = 1;
3597 config.output = OUTPUT_RAW;
3598 sdsfreesplitres(argv,argc);
3599 linenoiseFree(line);
3600 return; /* Return to evalMode to restart the session. */
3601 } else {
3602 printf("Use 'restart' only in Lua debugging mode.\n");
3603 fflush(stdout);
3604 }
3605 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
3606 sdsfree(config.conn_info.hostip);
3607 config.conn_info.hostip = sdsnew(argv[1]);
3608 config.conn_info.hostport = atoi(argv[2]);
3609 cliRefreshPrompt();
3610 cliConnect(CC_FORCE);
3611 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
3612 linenoiseClearScreen();
3613 } else {
3614 long long start_time = mstime(), elapsed;
3615
3616 issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);
3617
3618 /* If our debugging session ended, show the EVAL final
3619 * reply. */
3620 if (config.eval_ldb_end) {
3621 config.eval_ldb_end = 0;
3622 cliReadReply(0);
3623 printf("\n(Lua debugging session ended%s)\n\n",
3624 config.eval_ldb_sync ? "" :
3625 " -- dataset changes rolled back");
3626 cliInitHelp();
3627 }
3628
3629 elapsed = mstime()-start_time;
3630 if (elapsed >= 500 &&
3631 config.output == OUTPUT_STANDARD)
3632 {
3633 printf("(%.2fs)\n",(double)elapsed/1000);
3634 }
3635 }
3636 /* Free the argument vector */
3637 sdsfreesplitres(argv,argc);
3638 }
3639
3640 if (config.pubsub_mode) {
3641 cliWaitForMessagesOrStdin();
3642 }
3643
3644 /* linenoise() returns malloc-ed lines like readline() */
3645 linenoiseFree(line);
3646 }
3647 exit(0);
3648}
3649
3650static int noninteractive(int argc, char **argv) {
3651 int retval = 0;
3652 sds *sds_args = getSdsArrayFromArgv(argc, argv, config.quoted_input);
3653
3654 if (!sds_args) {
3655 printf("Invalid quoted string\n");
3656 return 1;
3657 }
3658
3659 if (config.stdin_lastarg) {
3660 sds_args = sds_realloc(sds_args, (argc + 1) * sizeof(sds));
3661 sds_args[argc] = readArgFromStdin();
3662 argc++;
3663 } else if (config.stdin_tag_arg) {
3664 int i = 0, tag_match = 0;
3665
3666 for (; i < argc; i++) {
3667 if (strcmp(config.stdin_tag_name, sds_args[i]) != 0) continue;
3668
3669 tag_match = 1;
3670 sdsfree(sds_args[i]);
3671 sds_args[i] = readArgFromStdin();
3672 break;
3673 }
3674
3675 if (!tag_match) {
3676 sdsfreesplitres(sds_args, argc);
3677 fprintf(stderr, "Using -X option but stdin tag not match.\n");
3678 return 1;
3679 }
3680 }
3681
3682 retval = issueCommand(argc, sds_args);
3683 sdsfreesplitres(sds_args, argc);
3684 while (config.pubsub_mode) {
3685 if (cliReadReply(0) != REDIS_OK) {
3686 cliPrintContextError();
3687 exit(1);
3688 }
3689 fflush(stdout);
3690 }
3691 return retval == REDIS_OK ? 0 : 1;
3692}
3693
3694static void longStatLoopModeStop(int s) {
3695 UNUSED(s);
3696 force_cancel_loop = 1;
3697}
3698
3699/*------------------------------------------------------------------------------
3700 * Eval mode
3701 *--------------------------------------------------------------------------- */
3702
3703static int evalMode(int argc, char **argv) {
3704 sds script = NULL;
3705 FILE *fp;
3706 char buf[1024];
3707 size_t nread;
3708 char **argv2;
3709 int j, got_comma, keys;
3710 int retval = REDIS_OK;
3711
3712 while(1) {
3713 if (config.eval_ldb) {
3714 printf(
3715 "Lua debugging session started, please use:\n"
3716 "quit -- End the session.\n"
3717 "restart -- Restart the script in debug mode again.\n"
3718 "help -- Show Lua script debugging commands.\n\n"
3719 );
3720 }
3721
3722 sdsfree(script);
3723 script = sdsempty();
3724 got_comma = 0;
3725 keys = 0;
3726
3727 /* Load the script from the file, as an sds string. */
3728 fp = fopen(config.eval,"r");
3729 if (!fp) {
3730 fprintf(stderr,
3731 "Can't open file '%s': %s\n", config.eval, strerror(errno));
3732 exit(1);
3733 }
3734 while((nread = fread(buf,1,sizeof(buf),fp)) != 0) {
3735 script = sdscatlen(script,buf,nread);
3736 }
3737 fclose(fp);
3738
3739 /* If we are debugging a script, enable the Lua debugger. */
3740 if (config.eval_ldb) {
3741 redisReply *reply = redisCommand(context,
3742 config.eval_ldb_sync ?
3743 "SCRIPT DEBUG sync": "SCRIPT DEBUG yes");
3744 if (reply) freeReplyObject(reply);
3745 }
3746
3747 /* Create our argument vector */
3748 argv2 = zmalloc(sizeof(sds)*(argc+3));
3749 argv2[0] = sdsnew("EVAL");
3750 argv2[1] = script;
3751 for (j = 0; j < argc; j++) {
3752 if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) {
3753 got_comma = 1;
3754 continue;
3755 }
3756 argv2[j+3-got_comma] = sdsnew(argv[j]);
3757 if (!got_comma) keys++;
3758 }
3759 argv2[2] = sdscatprintf(sdsempty(),"%d",keys);
3760
3761 /* Call it */
3762 int eval_ldb = config.eval_ldb; /* Save it, may be reverted. */
3763 retval = issueCommand(argc+3-got_comma, argv2);
3764 for (j = 0; j < argc+3-got_comma; j++) sdsfree(argv2[j]);
3765 zfree(argv2);
3766 if (eval_ldb) {
3767 if (!config.eval_ldb) {
3768 /* If the debugging session ended immediately, there was an
3769 * error compiling the script. Show it and they don't enter
3770 * the REPL at all. */
3771 printf("Eval debugging session can't start:\n");
3772 cliReadReply(0);
3773 break; /* Return to the caller. */
3774 } else {
3775 strncpy(config.prompt,"lua debugger> ",sizeof(config.prompt));
3776 repl();
3777 /* Restart the session if repl() returned. */
3778 cliConnect(CC_FORCE);
3779 printf("\n");
3780 }
3781 } else {
3782 break; /* Return to the caller. */
3783 }
3784 }
3785 return retval == REDIS_OK ? 0 : 1;
3786}
3787
3788/*------------------------------------------------------------------------------
3789 * Cluster Manager
3790 *--------------------------------------------------------------------------- */
3791
3792/* The Cluster Manager global structure */
3793static struct clusterManager {
3794 list *nodes; /* List of nodes in the configuration. */
3795 list *errors;
3796 int unreachable_masters; /* Masters we are not able to reach. */
3797} cluster_manager;
3798
3799/* Used by clusterManagerFixSlotsCoverage */
3800dict *clusterManagerUncoveredSlots = NULL;
3801
3802typedef struct clusterManagerNode {
3803 redisContext *context;
3804 sds name;
3805 char *ip;
3806 int port;
3807 int bus_port; /* cluster-port */
3808 uint64_t current_epoch;
3809 time_t ping_sent;
3810 time_t ping_recv;
3811 int flags;
3812 list *flags_str; /* Flags string representations */
3813 sds replicate; /* Master ID if node is a slave */
3814 int dirty; /* Node has changes that can be flushed */
3815 uint8_t slots[CLUSTER_MANAGER_SLOTS];
3816 int slots_count;
3817 int replicas_count;
3818 list *friends;
3819 sds *migrating; /* An array of sds where even strings are slots and odd
3820 * strings are the destination node IDs. */
3821 sds *importing; /* An array of sds where even strings are slots and odd
3822 * strings are the source node IDs. */
3823 int migrating_count; /* Length of the migrating array (migrating slots*2) */
3824 int importing_count; /* Length of the importing array (importing slots*2) */
3825 float weight; /* Weight used by rebalance */
3826 int balance; /* Used by rebalance */
3827} clusterManagerNode;
3828
3829/* Data structure used to represent a sequence of cluster nodes. */
3830typedef struct clusterManagerNodeArray {
3831 clusterManagerNode **nodes; /* Actual nodes array */
3832 clusterManagerNode **alloc; /* Pointer to the allocated memory */
3833 int len; /* Actual length of the array */
3834 int count; /* Non-NULL nodes count */
3835} clusterManagerNodeArray;
3836
3837/* Used for the reshard table. */
3838typedef struct clusterManagerReshardTableItem {
3839 clusterManagerNode *source;
3840 int slot;
3841} clusterManagerReshardTableItem;
3842
3843/* Info about a cluster internal link. */
3844
3845typedef struct clusterManagerLink {
3846 sds node_name;
3847 sds node_addr;
3848 int connected;
3849 int handshaking;
3850} clusterManagerLink;
3851
3852static dictType clusterManagerDictType = {
3853 dictSdsHash, /* hash function */
3854 NULL, /* key dup */
3855 NULL, /* val dup */
3856 dictSdsKeyCompare, /* key compare */
3857 NULL, /* key destructor */
3858 dictSdsDestructor, /* val destructor */
3859 NULL /* allow to expand */
3860};
3861
3862static dictType clusterManagerLinkDictType = {
3863 dictSdsHash, /* hash function */
3864 NULL, /* key dup */
3865 NULL, /* val dup */
3866 dictSdsKeyCompare, /* key compare */
3867 dictSdsDestructor, /* key destructor */
3868 dictListDestructor, /* val destructor */
3869 NULL /* allow to expand */
3870};
3871
3872typedef int clusterManagerCommandProc(int argc, char **argv);
3873typedef int (*clusterManagerOnReplyError)(redisReply *reply,
3874 clusterManagerNode *n, int bulk_idx);
3875
3876/* Cluster Manager helper functions */
3877
3878static clusterManagerNode *clusterManagerNewNode(char *ip, int port, int bus_port);
3879static clusterManagerNode *clusterManagerNodeByName(const char *name);
3880static clusterManagerNode *clusterManagerNodeByAbbreviatedName(const char *n);
3881static void clusterManagerNodeResetSlots(clusterManagerNode *node);
3882static int clusterManagerNodeIsCluster(clusterManagerNode *node, char **err);
3883static void clusterManagerPrintNotClusterNodeError(clusterManagerNode *node,
3884 char *err);
3885static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts,
3886 char **err);
3887static int clusterManagerLoadInfoFromNode(clusterManagerNode *node);
3888static int clusterManagerNodeIsEmpty(clusterManagerNode *node, char **err);
3889static int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
3890 int ip_count, clusterManagerNode ***offending, int *offending_len);
3891static void clusterManagerOptimizeAntiAffinity(clusterManagerNodeArray *ipnodes,
3892 int ip_count);
3893static sds clusterManagerNodeInfo(clusterManagerNode *node, int indent);
3894static void clusterManagerShowNodes(void);
3895static void clusterManagerShowClusterInfo(void);
3896static int clusterManagerFlushNodeConfig(clusterManagerNode *node, char **err);
3897static void clusterManagerWaitForClusterJoin(void);
3898static int clusterManagerCheckCluster(int quiet);
3899static void clusterManagerLog(int level, const char* fmt, ...);
3900static int clusterManagerIsConfigConsistent(void);
3901static dict *clusterManagerGetLinkStatus(void);
3902static void clusterManagerOnError(sds err);
3903static void clusterManagerNodeArrayInit(clusterManagerNodeArray *array,
3904 int len);
3905static void clusterManagerNodeArrayReset(clusterManagerNodeArray *array);
3906static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array,
3907 clusterManagerNode **nodeptr);
3908static void clusterManagerNodeArrayAdd(clusterManagerNodeArray *array,
3909 clusterManagerNode *node);
3910
3911/* Cluster Manager commands. */
3912
3913static int clusterManagerCommandCreate(int argc, char **argv);
3914static int clusterManagerCommandAddNode(int argc, char **argv);
3915static int clusterManagerCommandDeleteNode(int argc, char **argv);
3916static int clusterManagerCommandInfo(int argc, char **argv);
3917static int clusterManagerCommandCheck(int argc, char **argv);
3918static int clusterManagerCommandFix(int argc, char **argv);
3919static int clusterManagerCommandReshard(int argc, char **argv);
3920static int clusterManagerCommandRebalance(int argc, char **argv);
3921static int clusterManagerCommandSetTimeout(int argc, char **argv);
3922static int clusterManagerCommandImport(int argc, char **argv);
3923static int clusterManagerCommandCall(int argc, char **argv);
3924static int clusterManagerCommandHelp(int argc, char **argv);
3925static int clusterManagerCommandBackup(int argc, char **argv);
3926
3927typedef struct clusterManagerCommandDef {
3928 char *name;
3929 clusterManagerCommandProc *proc;
3930 int arity;
3931 char *args;
3932 char *options;
3933} clusterManagerCommandDef;
3934
3935clusterManagerCommandDef clusterManagerCommands[] = {
3936 {"create", clusterManagerCommandCreate, -1, "host1:port1 ... hostN:portN",
3937 "replicas <arg>"},
3938 {"check", clusterManagerCommandCheck, -1, "<host:port> or <host> <port> - separated by either colon or space",
3939 "search-multiple-owners"},
3940 {"info", clusterManagerCommandInfo, -1, "<host:port> or <host> <port> - separated by either colon or space", NULL},
3941 {"fix", clusterManagerCommandFix, -1, "<host:port> or <host> <port> - separated by either colon or space",
3942 "search-multiple-owners,fix-with-unreachable-masters"},
3943 {"reshard", clusterManagerCommandReshard, -1, "<host:port> or <host> <port> - separated by either colon or space",
3944 "from <arg>,to <arg>,slots <arg>,yes,timeout <arg>,pipeline <arg>,"
3945 "replace"},
3946 {"rebalance", clusterManagerCommandRebalance, -1, "<host:port> or <host> <port> - separated by either colon or space",
3947 "weight <node1=w1...nodeN=wN>,use-empty-masters,"
3948 "timeout <arg>,simulate,pipeline <arg>,threshold <arg>,replace"},
3949 {"add-node", clusterManagerCommandAddNode, 2,
3950 "new_host:new_port existing_host:existing_port", "slave,master-id <arg>"},
3951 {"del-node", clusterManagerCommandDeleteNode, 2, "host:port node_id",NULL},
3952 {"call", clusterManagerCommandCall, -2,
3953 "host:port command arg arg .. arg", "only-masters,only-replicas"},
3954 {"set-timeout", clusterManagerCommandSetTimeout, 2,
3955 "host:port milliseconds", NULL},
3956 {"import", clusterManagerCommandImport, 1, "host:port",
3957 "from <arg>,from-user <arg>,from-pass <arg>,from-askpass,copy,replace"},
3958 {"backup", clusterManagerCommandBackup, 2, "host:port backup_directory",
3959 NULL},
3960 {"help", clusterManagerCommandHelp, 0, NULL, NULL}
3961};
3962
3963typedef struct clusterManagerOptionDef {
3964 char *name;
3965 char *desc;
3966} clusterManagerOptionDef;
3967
3968clusterManagerOptionDef clusterManagerOptions[] = {
3969 {"--cluster-yes", "Automatic yes to cluster commands prompts"}
3970};
3971
3972static void getRDB(clusterManagerNode *node);
3973
3974static int createClusterManagerCommand(char *cmdname, int argc, char **argv) {
3975 clusterManagerCommand *cmd = &config.cluster_manager_command;
3976 cmd->name = cmdname;
3977 cmd->argc = argc;
3978 cmd->argv = argc ? argv : NULL;
3979 if (isColorTerm()) cmd->flags |= CLUSTER_MANAGER_CMD_FLAG_COLOR;
3980
3981 if (config.stdin_lastarg) {
3982 char **new_argv = zmalloc(sizeof(char*) * (cmd->argc+1));
3983 memcpy(new_argv, cmd->argv, sizeof(char*) * cmd->argc);
3984
3985 cmd->stdin_arg = readArgFromStdin();
3986 new_argv[cmd->argc++] = cmd->stdin_arg;
3987 cmd->argv = new_argv;
3988 } else if (config.stdin_tag_arg) {
3989 int i = 0, tag_match = 0;
3990 cmd->stdin_arg = readArgFromStdin();
3991
3992 for (; i < argc; i++) {
3993 if (strcmp(argv[i], config.stdin_tag_name) != 0) continue;
3994
3995 tag_match = 1;
3996 cmd->argv[i] = (char *)cmd->stdin_arg;
3997 break;
3998 }
3999
4000 if (!tag_match) {
4001 sdsfree(cmd->stdin_arg);
4002 fprintf(stderr, "Using -X option but stdin tag not match.\n");
4003 return 1;
4004 }
4005 }
4006
4007 return 0;
4008}
4009
4010static clusterManagerCommandProc *validateClusterManagerCommand(void) {
4011 int i, commands_count = sizeof(clusterManagerCommands) /
4012 sizeof(clusterManagerCommandDef);
4013 clusterManagerCommandProc *proc = NULL;
4014 char *cmdname = config.cluster_manager_command.name;
4015 int argc = config.cluster_manager_command.argc;
4016 for (i = 0; i < commands_count; i++) {
4017 clusterManagerCommandDef cmddef = clusterManagerCommands[i];
4018 if (!strcmp(cmddef.name, cmdname)) {
4019 if ((cmddef.arity > 0 && argc != cmddef.arity) ||
4020 (cmddef.arity < 0 && argc < (cmddef.arity * -1))) {
4021 fprintf(stderr, "[ERR] Wrong number of arguments for "
4022 "specified --cluster sub command\n");
4023 return NULL;
4024 }
4025 proc = cmddef.proc;
4026 }
4027 }
4028 if (!proc) fprintf(stderr, "Unknown --cluster subcommand\n");
4029 return proc;
4030}
4031
4032static int parseClusterNodeAddress(char *addr, char **ip_ptr, int *port_ptr,
4033 int *bus_port_ptr)
4034{
4035 /* ip:port[@bus_port] */
4036 char *c = strrchr(addr, '@');
4037 if (c != NULL) {
4038 *c = '\0';
4039 if (bus_port_ptr != NULL)
4040 *bus_port_ptr = atoi(c + 1);
4041 }
4042 c = strrchr(addr, ':');
4043 if (c != NULL) {
4044 *c = '\0';
4045 *ip_ptr = addr;
4046 *port_ptr = atoi(++c);
4047 } else return 0;
4048 return 1;
4049}
4050
4051/* Get host ip and port from command arguments. If only one argument has
4052 * been provided it must be in the form of 'ip:port', elsewhere
4053 * the first argument must be the ip and the second one the port.
4054 * If host and port can be detected, it returns 1 and it stores host and
4055 * port into variables referenced by 'ip_ptr' and 'port_ptr' pointers,
4056 * elsewhere it returns 0. */
4057static int getClusterHostFromCmdArgs(int argc, char **argv,
4058 char **ip_ptr, int *port_ptr) {
4059 int port = 0;
4060 char *ip = NULL;
4061 if (argc == 1) {
4062 char *addr = argv[0];
4063 if (!parseClusterNodeAddress(addr, &ip, &port, NULL)) return 0;
4064 } else {
4065 ip = argv[0];
4066 port = atoi(argv[1]);
4067 }
4068 if (!ip || !port) return 0;
4069 else {
4070 *ip_ptr = ip;
4071 *port_ptr = port;
4072 }
4073 return 1;
4074}
4075
4076static void freeClusterManagerNodeFlags(list *flags) {
4077 listIter li;
4078 listNode *ln;
4079 listRewind(flags, &li);
4080 while ((ln = listNext(&li)) != NULL) {
4081 sds flag = ln->value;
4082 sdsfree(flag);
4083 }
4084 listRelease(flags);
4085}
4086
4087static void freeClusterManagerNode(clusterManagerNode *node) {
4088 if (node->context != NULL) redisFree(node->context);
4089 if (node->friends != NULL) {
4090 listIter li;
4091 listNode *ln;
4092 listRewind(node->friends,&li);
4093 while ((ln = listNext(&li)) != NULL) {
4094 clusterManagerNode *fn = ln->value;
4095 freeClusterManagerNode(fn);
4096 }
4097 listRelease(node->friends);
4098 node->friends = NULL;
4099 }
4100 if (node->name != NULL) sdsfree(node->name);
4101 if (node->replicate != NULL) sdsfree(node->replicate);
4102 if ((node->flags & CLUSTER_MANAGER_FLAG_FRIEND) && node->ip)
4103 sdsfree(node->ip);
4104 int i;
4105 if (node->migrating != NULL) {
4106 for (i = 0; i < node->migrating_count; i++) sdsfree(node->migrating[i]);
4107 zfree(node->migrating);
4108 }
4109 if (node->importing != NULL) {
4110 for (i = 0; i < node->importing_count; i++) sdsfree(node->importing[i]);
4111 zfree(node->importing);
4112 }
4113 if (node->flags_str != NULL) {
4114 freeClusterManagerNodeFlags(node->flags_str);
4115 node->flags_str = NULL;
4116 }
4117 zfree(node);
4118}
4119
4120static void freeClusterManager(void) {
4121 listIter li;
4122 listNode *ln;
4123 if (cluster_manager.nodes != NULL) {
4124 listRewind(cluster_manager.nodes,&li);
4125 while ((ln = listNext(&li)) != NULL) {
4126 clusterManagerNode *n = ln->value;
4127 freeClusterManagerNode(n);
4128 }
4129 listRelease(cluster_manager.nodes);
4130 cluster_manager.nodes = NULL;
4131 }
4132 if (cluster_manager.errors != NULL) {
4133 listRewind(cluster_manager.errors,&li);
4134 while ((ln = listNext(&li)) != NULL) {
4135 sds err = ln->value;
4136 sdsfree(err);
4137 }
4138 listRelease(cluster_manager.errors);
4139 cluster_manager.errors = NULL;
4140 }
4141 if (clusterManagerUncoveredSlots != NULL)
4142 dictRelease(clusterManagerUncoveredSlots);
4143}
4144
4145static clusterManagerNode *clusterManagerNewNode(char *ip, int port, int bus_port) {
4146 clusterManagerNode *node = zmalloc(sizeof(*node));
4147 node->context = NULL;
4148 node->name = NULL;
4149 node->ip = ip;
4150 node->port = port;
4151 /* We don't need to know the bus_port, at this point this value may be wrong.
4152 * If it is used, it will be corrected in clusterManagerLoadInfoFromNode. */
4153 node->bus_port = bus_port ? bus_port : port + CLUSTER_MANAGER_PORT_INCR;
4154 node->current_epoch = 0;
4155 node->ping_sent = 0;
4156 node->ping_recv = 0;
4157 node->flags = 0;
4158 node->flags_str = NULL;
4159 node->replicate = NULL;
4160 node->dirty = 0;
4161 node->friends = NULL;
4162 node->migrating = NULL;
4163 node->importing = NULL;
4164 node->migrating_count = 0;
4165 node->importing_count = 0;
4166 node->replicas_count = 0;
4167 node->weight = 1.0f;
4168 node->balance = 0;
4169 clusterManagerNodeResetSlots(node);
4170 return node;
4171}
4172
4173static sds clusterManagerGetNodeRDBFilename(clusterManagerNode *node) {
4174 assert(config.cluster_manager_command.backup_dir);
4175 sds filename = sdsnew(config.cluster_manager_command.backup_dir);
4176 if (filename[sdslen(filename) - 1] != '/')
4177 filename = sdscat(filename, "/");
4178 filename = sdscatprintf(filename, "redis-node-%s-%d-%s.rdb", node->ip,
4179 node->port, node->name);
4180 return filename;
4181}
4182
4183/* Check whether reply is NULL or its type is REDIS_REPLY_ERROR. In the
4184 * latest case, if the 'err' arg is not NULL, it gets allocated with a copy
4185 * of reply error (it's up to the caller function to free it), elsewhere
4186 * the error is directly printed. */
4187static int clusterManagerCheckRedisReply(clusterManagerNode *n,
4188 redisReply *r, char **err)
4189{
4190 int is_err = 0;
4191 if (!r || (is_err = (r->type == REDIS_REPLY_ERROR))) {
4192 if (is_err) {
4193 if (err != NULL) {
4194 *err = zmalloc((r->len + 1) * sizeof(char));
4195 redis_strlcpy(*err, r->str,(r->len + 1));
4196 } else CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, r->str);
4197 }
4198 return 0;
4199 }
4200 return 1;
4201}
4202
4203/* Call MULTI command on a cluster node. */
4204static int clusterManagerStartTransaction(clusterManagerNode *node) {
4205 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "MULTI");
4206 int success = clusterManagerCheckRedisReply(node, reply, NULL);
4207 if (reply) freeReplyObject(reply);
4208 return success;
4209}
4210
4211/* Call EXEC command on a cluster node. */
4212static int clusterManagerExecTransaction(clusterManagerNode *node,
4213 clusterManagerOnReplyError onerror)
4214{
4215 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "EXEC");
4216 int success = clusterManagerCheckRedisReply(node, reply, NULL);
4217 if (success) {
4218 if (reply->type != REDIS_REPLY_ARRAY) {
4219 success = 0;
4220 goto cleanup;
4221 }
4222 size_t i;
4223 for (i = 0; i < reply->elements; i++) {
4224 redisReply *r = reply->element[i];
4225 char *err = NULL;
4226 success = clusterManagerCheckRedisReply(node, r, &err);
4227 if (!success && onerror) success = onerror(r, node, i);
4228 if (err) {
4229 if (!success)
4230 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
4231 zfree(err);
4232 }
4233 if (!success) break;
4234 }
4235 }
4236cleanup:
4237 if (reply) freeReplyObject(reply);
4238 return success;
4239}
4240
4241static int clusterManagerNodeConnect(clusterManagerNode *node) {
4242 if (node->context) redisFree(node->context);
4243 node->context = redisConnectWrapper(node->ip, node->port, config.connect_timeout);
4244 if (!node->context->err && config.tls) {
4245 const char *err = NULL;
4246 if (cliSecureConnection(node->context, config.sslconfig, &err) == REDIS_ERR && err) {
4247 fprintf(stderr,"TLS Error: %s\n", err);
4248 redisFree(node->context);
4249 node->context = NULL;
4250 return 0;
4251 }
4252 }
4253 if (node->context->err) {
4254 fprintf(stderr,"Could not connect to Redis at ");
4255 fprintf(stderr,"%s:%d: %s\n", node->ip, node->port,
4256 node->context->errstr);
4257 redisFree(node->context);
4258 node->context = NULL;
4259 return 0;
4260 }
4261 /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
4262 * in order to prevent timeouts caused by the execution of long
4263 * commands. At the same time this improves the detection of real
4264 * errors. */
4265 anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
4266 if (config.conn_info.auth) {
4267 redisReply *reply;
4268 if (config.conn_info.user == NULL)
4269 reply = redisCommand(node->context,"AUTH %s", config.conn_info.auth);
4270 else
4271 reply = redisCommand(node->context,"AUTH %s %s",
4272 config.conn_info.user,config.conn_info.auth);
4273 int ok = clusterManagerCheckRedisReply(node, reply, NULL);
4274 if (reply != NULL) freeReplyObject(reply);
4275 if (!ok) return 0;
4276 }
4277 return 1;
4278}
4279
4280static void clusterManagerRemoveNodeFromList(list *nodelist,
4281 clusterManagerNode *node) {
4282 listIter li;
4283 listNode *ln;
4284 listRewind(nodelist, &li);
4285 while ((ln = listNext(&li)) != NULL) {
4286 if (node == ln->value) {
4287 listDelNode(nodelist, ln);
4288 break;
4289 }
4290 }
4291}
4292
4293/* Return the node with the specified name (ID) or NULL. */
4294static clusterManagerNode *clusterManagerNodeByName(const char *name) {
4295 if (cluster_manager.nodes == NULL) return NULL;
4296 clusterManagerNode *found = NULL;
4297 sds lcname = sdsempty();
4298 lcname = sdscpy(lcname, name);
4299 sdstolower(lcname);
4300 listIter li;
4301 listNode *ln;
4302 listRewind(cluster_manager.nodes, &li);
4303 while ((ln = listNext(&li)) != NULL) {
4304 clusterManagerNode *n = ln->value;
4305 if (n->name && !sdscmp(n->name, lcname)) {
4306 found = n;
4307 break;
4308 }
4309 }
4310 sdsfree(lcname);
4311 return found;
4312}
4313
4314/* Like clusterManagerNodeByName but the specified name can be just the first
4315 * part of the node ID as long as the prefix in unique across the
4316 * cluster.
4317 */
4318static clusterManagerNode *clusterManagerNodeByAbbreviatedName(const char*name)
4319{
4320 if (cluster_manager.nodes == NULL) return NULL;
4321 clusterManagerNode *found = NULL;
4322 sds lcname = sdsempty();
4323 lcname = sdscpy(lcname, name);
4324 sdstolower(lcname);
4325 listIter li;
4326 listNode *ln;
4327 listRewind(cluster_manager.nodes, &li);
4328 while ((ln = listNext(&li)) != NULL) {
4329 clusterManagerNode *n = ln->value;
4330 if (n->name &&
4331 strstr(n->name, lcname) == n->name) {
4332 found = n;
4333 break;
4334 }
4335 }
4336 sdsfree(lcname);
4337 return found;
4338}
4339
4340static void clusterManagerNodeResetSlots(clusterManagerNode *node) {
4341 memset(node->slots, 0, sizeof(node->slots));
4342 node->slots_count = 0;
4343}
4344
4345/* Call "INFO" redis command on the specified node and return the reply. */
4346static redisReply *clusterManagerGetNodeRedisInfo(clusterManagerNode *node,
4347 char **err)
4348{
4349 redisReply *info = CLUSTER_MANAGER_COMMAND(node, "INFO");
4350 if (err != NULL) *err = NULL;
4351 if (info == NULL) return NULL;
4352 if (info->type == REDIS_REPLY_ERROR) {
4353 if (err != NULL) {
4354 *err = zmalloc((info->len + 1) * sizeof(char));
4355 redis_strlcpy(*err, info->str,(info->len + 1));
4356 }
4357 freeReplyObject(info);
4358 return NULL;
4359 }
4360 return info;
4361}
4362
4363static int clusterManagerNodeIsCluster(clusterManagerNode *node, char **err) {
4364 redisReply *info = clusterManagerGetNodeRedisInfo(node, err);
4365 if (info == NULL) return 0;
4366 int is_cluster = (int) getLongInfoField(info->str, "cluster_enabled");
4367 freeReplyObject(info);
4368 return is_cluster;
4369}
4370
4371/* Checks whether the node is empty. Node is considered not-empty if it has
4372 * some key or if it already knows other nodes */
4373static int clusterManagerNodeIsEmpty(clusterManagerNode *node, char **err) {
4374 redisReply *info = clusterManagerGetNodeRedisInfo(node, err);
4375 int is_empty = 1;
4376 if (info == NULL) return 0;
4377 if (strstr(info->str, "db0:") != NULL) {
4378 is_empty = 0;
4379 goto result;
4380 }
4381 freeReplyObject(info);
4382 info = CLUSTER_MANAGER_COMMAND(node, "CLUSTER INFO");
4383 if (err != NULL) *err = NULL;
4384 if (!clusterManagerCheckRedisReply(node, info, err)) {
4385 is_empty = 0;
4386 goto result;
4387 }
4388 long known_nodes = getLongInfoField(info->str, "cluster_known_nodes");
4389 is_empty = (known_nodes == 1);
4390result:
4391 freeReplyObject(info);
4392 return is_empty;
4393}
4394
4395/* Return the anti-affinity score, which is a measure of the amount of
4396 * violations of anti-affinity in the current cluster layout, that is, how
4397 * badly the masters and slaves are distributed in the different IP
4398 * addresses so that slaves of the same master are not in the master
4399 * host and are also in different hosts.
4400 *
4401 * The score is calculated as follows:
4402 *
4403 * SAME_AS_MASTER = 10000 * each slave in the same IP of its master.
4404 * SAME_AS_SLAVE = 1 * each slave having the same IP as another slave
4405 of the same master.
4406 * FINAL_SCORE = SAME_AS_MASTER + SAME_AS_SLAVE
4407 *
4408 * So a greater score means a worse anti-affinity level, while zero
4409 * means perfect anti-affinity.
4410 *
4411 * The anti affinity optimization will try to get a score as low as
4412 * possible. Since we do not want to sacrifice the fact that slaves should
4413 * not be in the same host as the master, we assign 10000 times the score
4414 * to this violation, so that we'll optimize for the second factor only
4415 * if it does not impact the first one.
4416 *
4417 * The ipnodes argument is an array of clusterManagerNodeArray, one for
4418 * each IP, while ip_count is the total number of IPs in the configuration.
4419 *
4420 * The function returns the above score, and the list of
4421 * offending slaves can be stored into the 'offending' argument,
4422 * so that the optimizer can try changing the configuration of the
4423 * slaves violating the anti-affinity goals. */
4424static int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
4425 int ip_count, clusterManagerNode ***offending, int *offending_len)
4426{
4427 int score = 0, i, j;
4428 int node_len = cluster_manager.nodes->len;
4429 clusterManagerNode **offending_p = NULL;
4430 if (offending != NULL) {
4431 *offending = zcalloc(node_len * sizeof(clusterManagerNode*));
4432 offending_p = *offending;
4433 }
4434 /* For each set of nodes in the same host, split by
4435 * related nodes (masters and slaves which are involved in
4436 * replication of each other) */
4437 for (i = 0; i < ip_count; i++) {
4438 clusterManagerNodeArray *node_array = &(ipnodes[i]);
4439 dict *related = dictCreate(&clusterManagerDictType);
4440 char *ip = NULL;
4441 for (j = 0; j < node_array->len; j++) {
4442 clusterManagerNode *node = node_array->nodes[j];
4443 if (node == NULL) continue;
4444 if (!ip) ip = node->ip;
4445 sds types;
4446 /* We always use the Master ID as key. */
4447 sds key = (!node->replicate ? node->name : node->replicate);
4448 assert(key != NULL);
4449 dictEntry *entry = dictFind(related, key);
4450 if (entry) types = sdsdup((sds) dictGetVal(entry));
4451 else types = sdsempty();
4452 /* Master type 'm' is always set as the first character of the
4453 * types string. */
4454 if (node->replicate) types = sdscat(types, "s");
4455 else {
4456 sds s = sdscatsds(sdsnew("m"), types);
4457 sdsfree(types);
4458 types = s;
4459 }
4460 dictReplace(related, key, types);
4461 }
4462 /* Now it's trivial to check, for each related group having the
4463 * same host, what is their local score. */
4464 dictIterator iter;
4465 dictEntry *entry;
4466
4467 dictInitIterator(&iter, related);
4468 while ((entry = dictNext(&iter)) != NULL) {
4469 sds types = (sds) dictGetVal(entry);
4470 sds name = (sds) dictGetKey(entry);
4471 int typeslen = sdslen(types);
4472 if (typeslen < 2) continue;
4473 if (types[0] == 'm') score += (10000 * (typeslen - 1));
4474 else score += (1 * typeslen);
4475 if (offending == NULL) continue;
4476 /* Populate the list of offending nodes. */
4477 listIter li;
4478 listNode *ln;
4479 listRewind(cluster_manager.nodes, &li);
4480 while ((ln = listNext(&li)) != NULL) {
4481 clusterManagerNode *n = ln->value;
4482 if (n->replicate == NULL) continue;
4483 if (!strcmp(n->replicate, name) && !strcmp(n->ip, ip)) {
4484 *(offending_p++) = n;
4485 if (offending_len != NULL) (*offending_len)++;
4486 break;
4487 }
4488 }
4489 }
4490 //if (offending_len != NULL) *offending_len = offending_p - *offending;
4491 dictResetIterator(&iter);
4492 dictRelease(related);
4493 }
4494 return score;
4495}
4496
4497static void clusterManagerOptimizeAntiAffinity(clusterManagerNodeArray *ipnodes,
4498 int ip_count)
4499{
4500 clusterManagerNode **offenders = NULL;
4501 int score = clusterManagerGetAntiAffinityScore(ipnodes, ip_count,
4502 NULL, NULL);
4503 if (score == 0) goto cleanup;
4504 clusterManagerLogInfo(">>> Trying to optimize slaves allocation "
4505 "for anti-affinity\n");
4506 int node_len = cluster_manager.nodes->len;
4507 int maxiter = 500 * node_len; // Effort is proportional to cluster size...
4508 srand(time(NULL));
4509 while (maxiter > 0) {
4510 int offending_len = 0;
4511 if (offenders != NULL) {
4512 zfree(offenders);
4513 offenders = NULL;
4514 }
4515 score = clusterManagerGetAntiAffinityScore(ipnodes,
4516 ip_count,
4517 &offenders,
4518 &offending_len);
4519 if (score == 0 || offending_len == 0) break; // Optimal anti affinity reached
4520 /* We'll try to randomly swap a slave's assigned master causing
4521 * an affinity problem with another random slave, to see if we
4522 * can improve the affinity. */
4523 int rand_idx = rand() % offending_len;
4524 clusterManagerNode *first = offenders[rand_idx],
4525 *second = NULL;
4526 clusterManagerNode **other_replicas = zcalloc((node_len - 1) *
4527 sizeof(*other_replicas));
4528 int other_replicas_count = 0;
4529 listIter li;
4530 listNode *ln;
4531 listRewind(cluster_manager.nodes, &li);
4532 while ((ln = listNext(&li)) != NULL) {
4533 clusterManagerNode *n = ln->value;
4534 if (n != first && n->replicate != NULL)
4535 other_replicas[other_replicas_count++] = n;
4536 }
4537 if (other_replicas_count == 0) {
4538 zfree(other_replicas);
4539 break;
4540 }
4541 rand_idx = rand() % other_replicas_count;
4542 second = other_replicas[rand_idx];
4543 char *first_master = first->replicate,
4544 *second_master = second->replicate;
4545 first->replicate = second_master, first->dirty = 1;
4546 second->replicate = first_master, second->dirty = 1;
4547 int new_score = clusterManagerGetAntiAffinityScore(ipnodes,
4548 ip_count,
4549 NULL, NULL);
4550 /* If the change actually makes thing worse, revert. Otherwise
4551 * leave as it is because the best solution may need a few
4552 * combined swaps. */
4553 if (new_score > score) {
4554 first->replicate = first_master;
4555 second->replicate = second_master;
4556 }
4557 zfree(other_replicas);
4558 maxiter--;
4559 }
4560 score = clusterManagerGetAntiAffinityScore(ipnodes, ip_count, NULL, NULL);
4561 char *msg;
4562 int perfect = (score == 0);
4563 int log_level = (perfect ? CLUSTER_MANAGER_LOG_LVL_SUCCESS :
4564 CLUSTER_MANAGER_LOG_LVL_WARN);
4565 if (perfect) msg = "[OK] Perfect anti-affinity obtained!";
4566 else if (score >= 10000)
4567 msg = ("[WARNING] Some slaves are in the same host as their master");
4568 else
4569 msg=("[WARNING] Some slaves of the same master are in the same host");
4570 clusterManagerLog(log_level, "%s\n", msg);
4571cleanup:
4572 zfree(offenders);
4573}
4574
4575/* Return a representable string of the node's flags */
4576static sds clusterManagerNodeFlagString(clusterManagerNode *node) {
4577 sds flags = sdsempty();
4578 if (!node->flags_str) return flags;
4579 int empty = 1;
4580 listIter li;
4581 listNode *ln;
4582 listRewind(node->flags_str, &li);
4583 while ((ln = listNext(&li)) != NULL) {
4584 sds flag = ln->value;
4585 if (strcmp(flag, "myself") == 0) continue;
4586 if (!empty) flags = sdscat(flags, ",");
4587 flags = sdscatfmt(flags, "%S", flag);
4588 empty = 0;
4589 }
4590 return flags;
4591}
4592
4593/* Return a representable string of the node's slots */
4594static sds clusterManagerNodeSlotsString(clusterManagerNode *node) {
4595 sds slots = sdsempty();
4596 int first_range_idx = -1, last_slot_idx = -1, i;
4597 for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
4598 int has_slot = node->slots[i];
4599 if (has_slot) {
4600 if (first_range_idx == -1) {
4601 if (sdslen(slots)) slots = sdscat(slots, ",");
4602 first_range_idx = i;
4603 slots = sdscatfmt(slots, "[%u", i);
4604 }
4605 last_slot_idx = i;
4606 } else {
4607 if (last_slot_idx >= 0) {
4608 if (first_range_idx == last_slot_idx)
4609 slots = sdscat(slots, "]");
4610 else slots = sdscatfmt(slots, "-%u]", last_slot_idx);
4611 }
4612 last_slot_idx = -1;
4613 first_range_idx = -1;
4614 }
4615 }
4616 if (last_slot_idx >= 0) {
4617 if (first_range_idx == last_slot_idx) slots = sdscat(slots, "]");
4618 else slots = sdscatfmt(slots, "-%u]", last_slot_idx);
4619 }
4620 return slots;
4621}
4622
4623static sds clusterManagerNodeGetJSON(clusterManagerNode *node,
4624 unsigned long error_count)
4625{
4626 sds json = sdsempty();
4627 sds replicate = sdsempty();
4628 if (node->replicate)
4629 replicate = sdscatprintf(replicate, "\"%s\"", node->replicate);
4630 else
4631 replicate = sdscat(replicate, "null");
4632 sds slots = clusterManagerNodeSlotsString(node);
4633 sds flags = clusterManagerNodeFlagString(node);
4634 char *p = slots;
4635 while ((p = strchr(p, '-')) != NULL)
4636 *(p++) = ',';
4637 json = sdscatprintf(json,
4638 " {\n"
4639 " \"name\": \"%s\",\n"
4640 " \"host\": \"%s\",\n"
4641 " \"port\": %d,\n"
4642 " \"replicate\": %s,\n"
4643 " \"slots\": [%s],\n"
4644 " \"slots_count\": %d,\n"
4645 " \"flags\": \"%s\",\n"
4646 " \"current_epoch\": %llu",
4647 node->name,
4648 node->ip,
4649 node->port,
4650 replicate,
4651 slots,
4652 node->slots_count,
4653 flags,
4654 (unsigned long long)node->current_epoch
4655 );
4656 if (error_count > 0) {
4657 json = sdscatprintf(json, ",\n \"cluster_errors\": %lu",
4658 error_count);
4659 }
4660 if (node->migrating_count > 0 && node->migrating != NULL) {
4661 int i = 0;
4662 sds migrating = sdsempty();
4663 for (; i < node->migrating_count; i += 2) {
4664 sds slot = node->migrating[i];
4665 sds dest = node->migrating[i + 1];
4666 if (slot && dest) {
4667 if (sdslen(migrating) > 0) migrating = sdscat(migrating, ",");
4668 migrating = sdscatfmt(migrating, "\"%S\": \"%S\"", slot, dest);
4669 }
4670 }
4671 if (sdslen(migrating) > 0)
4672 json = sdscatfmt(json, ",\n \"migrating\": {%S}", migrating);
4673 sdsfree(migrating);
4674 }
4675 if (node->importing_count > 0 && node->importing != NULL) {
4676 int i = 0;
4677 sds importing = sdsempty();
4678 for (; i < node->importing_count; i += 2) {
4679 sds slot = node->importing[i];
4680 sds from = node->importing[i + 1];
4681 if (slot && from) {
4682 if (sdslen(importing) > 0) importing = sdscat(importing, ",");
4683 importing = sdscatfmt(importing, "\"%S\": \"%S\"", slot, from);
4684 }
4685 }
4686 if (sdslen(importing) > 0)
4687 json = sdscatfmt(json, ",\n \"importing\": {%S}", importing);
4688 sdsfree(importing);
4689 }
4690 json = sdscat(json, "\n }");
4691 sdsfree(replicate);
4692 sdsfree(slots);
4693 sdsfree(flags);
4694 return json;
4695}
4696
4697
4698/* -----------------------------------------------------------------------------
4699 * Key space handling
4700 * -------------------------------------------------------------------------- */
4701
4702/* We have 16384 hash slots. The hash slot of a given key is obtained
4703 * as the least significant 14 bits of the crc16 of the key.
4704 *
4705 * However if the key contains the {...} pattern, only the part between
4706 * { and } is hashed. This may be useful in the future to force certain
4707 * keys to be in the same node (assuming no resharding is in progress). */
4708static unsigned int clusterManagerKeyHashSlot(char *key, int keylen) {
4709 int s, e; /* start-end indexes of { and } */
4710
4711 for (s = 0; s < keylen; s++)
4712 if (key[s] == '{') break;
4713
4714 /* No '{' ? Hash the whole key. This is the base case. */
4715 if (s == keylen) return crc16(key,keylen) & 0x3FFF;
4716
4717 /* '{' found? Check if we have the corresponding '}'. */
4718 for (e = s+1; e < keylen; e++)
4719 if (key[e] == '}') break;
4720
4721 /* No '}' or nothing between {} ? Hash the whole key. */
4722 if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;
4723
4724 /* If we are here there is both a { and a } on its right. Hash
4725 * what is in the middle between { and }. */
4726 return crc16(key+s+1,e-s-1) & 0x3FFF;
4727}
4728
4729/* Return a string representation of the cluster node. */
4730static sds clusterManagerNodeInfo(clusterManagerNode *node, int indent) {
4731 sds info = sdsempty();
4732 sds spaces = sdsempty();
4733 int i;
4734 for (i = 0; i < indent; i++) spaces = sdscat(spaces, " ");
4735 if (indent) info = sdscat(info, spaces);
4736 int is_master = !(node->flags & CLUSTER_MANAGER_FLAG_SLAVE);
4737 char *role = (is_master ? "M" : "S");
4738 sds slots = NULL;
4739 if (node->dirty && node->replicate != NULL)
4740 info = sdscatfmt(info, "S: %S %s:%u", node->name, node->ip, node->port);
4741 else {
4742 slots = clusterManagerNodeSlotsString(node);
4743 sds flags = clusterManagerNodeFlagString(node);
4744 info = sdscatfmt(info, "%s: %S %s:%u\n"
4745 "%s slots:%S (%u slots) "
4746 "%S",
4747 role, node->name, node->ip, node->port, spaces,
4748 slots, node->slots_count, flags);
4749 sdsfree(slots);
4750 sdsfree(flags);
4751 }
4752 if (node->replicate != NULL)
4753 info = sdscatfmt(info, "\n%s replicates %S", spaces, node->replicate);
4754 else if (node->replicas_count)
4755 info = sdscatfmt(info, "\n%s %U additional replica(s)",
4756 spaces, node->replicas_count);
4757 sdsfree(spaces);
4758 return info;
4759}
4760
4761static void clusterManagerShowNodes(void) {
4762 listIter li;
4763 listNode *ln;
4764 listRewind(cluster_manager.nodes, &li);
4765 while ((ln = listNext(&li)) != NULL) {
4766 clusterManagerNode *node = ln->value;
4767 sds info = clusterManagerNodeInfo(node, 0);
4768 printf("%s\n", (char *) info);
4769 sdsfree(info);
4770 }
4771}
4772
4773static void clusterManagerShowClusterInfo(void) {
4774 int masters = 0;
4775 long long keys = 0;
4776 listIter li;
4777 listNode *ln;
4778 listRewind(cluster_manager.nodes, &li);
4779 while ((ln = listNext(&li)) != NULL) {
4780 clusterManagerNode *node = ln->value;
4781 if (!(node->flags & CLUSTER_MANAGER_FLAG_SLAVE)) {
4782 if (!node->name) continue;
4783 int replicas = 0;
4784 long long dbsize = -1;
4785 char name[9];
4786 memcpy(name, node->name, 8);
4787 name[8] = '\0';
4788 listIter ri;
4789 listNode *rn;
4790 listRewind(cluster_manager.nodes, &ri);
4791 while ((rn = listNext(&ri)) != NULL) {
4792 clusterManagerNode *n = rn->value;
4793 if (n == node || !(n->flags & CLUSTER_MANAGER_FLAG_SLAVE))
4794 continue;
4795 if (n->replicate && !strcmp(n->replicate, node->name))
4796 replicas++;
4797 }
4798 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "DBSIZE");
4799 if (reply != NULL && reply->type == REDIS_REPLY_INTEGER)
4800 dbsize = reply->integer;
4801 if (dbsize < 0) {
4802 char *err = "";
4803 if (reply != NULL && reply->type == REDIS_REPLY_ERROR)
4804 err = reply->str;
4805 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
4806 if (reply != NULL) freeReplyObject(reply);
4807 return;
4808 };
4809 if (reply != NULL) freeReplyObject(reply);
4810 printf("%s:%d (%s...) -> %lld keys | %d slots | %d slaves.\n",
4811 node->ip, node->port, name, dbsize,
4812 node->slots_count, replicas);
4813 masters++;
4814 keys += dbsize;
4815 }
4816 }
4817 clusterManagerLogOk("[OK] %lld keys in %d masters.\n", keys, masters);
4818 float keys_per_slot = keys / (float) CLUSTER_MANAGER_SLOTS;
4819 printf("%.2f keys per slot on average.\n", keys_per_slot);
4820}
4821
4822/* Flush dirty slots configuration of the node by calling CLUSTER ADDSLOTS */
4823static int clusterManagerAddSlots(clusterManagerNode *node, char**err)
4824{
4825 redisReply *reply = NULL;
4826 void *_reply = NULL;
4827 int success = 1;
4828 /* First two args are used for the command itself. */
4829 int argc = node->slots_count + 2;
4830 sds *argv = zmalloc(argc * sizeof(*argv));
4831 size_t *argvlen = zmalloc(argc * sizeof(*argvlen));
4832 argv[0] = "CLUSTER";
4833 argv[1] = "ADDSLOTS";
4834 argvlen[0] = 7;
4835 argvlen[1] = 8;
4836 *err = NULL;
4837 int i, argv_idx = 2;
4838 for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
4839 if (argv_idx >= argc) break;
4840 if (node->slots[i]) {
4841 argv[argv_idx] = sdsfromlonglong((long long) i);
4842 argvlen[argv_idx] = sdslen(argv[argv_idx]);
4843 argv_idx++;
4844 }
4845 }
4846 if (argv_idx == 2) {
4847 success = 0;
4848 goto cleanup;
4849 }
4850 redisAppendCommandArgv(node->context,argc,(const char**)argv,argvlen);
4851 if (redisGetReply(node->context, &_reply) != REDIS_OK) {
4852 success = 0;
4853 goto cleanup;
4854 }
4855 reply = (redisReply*) _reply;
4856 success = clusterManagerCheckRedisReply(node, reply, err);
4857cleanup:
4858 zfree(argvlen);
4859 if (argv != NULL) {
4860 for (i = 2; i < argc; i++) sdsfree(argv[i]);
4861 zfree(argv);
4862 }
4863 if (reply != NULL) freeReplyObject(reply);
4864 return success;
4865}
4866
4867/* Get the node the slot is assigned to from the point of view of node *n.
4868 * If the slot is unassigned or if the reply is an error, return NULL.
4869 * Use the **err argument in order to check whether the slot is unassigned
4870 * or the reply resulted in an error. */
4871static clusterManagerNode *clusterManagerGetSlotOwner(clusterManagerNode *n,
4872 int slot, char **err)
4873{
4874 assert(slot >= 0 && slot < CLUSTER_MANAGER_SLOTS);
4875 clusterManagerNode *owner = NULL;
4876 redisReply *reply = CLUSTER_MANAGER_COMMAND(n, "CLUSTER SLOTS");
4877 if (clusterManagerCheckRedisReply(n, reply, err)) {
4878 assert(reply->type == REDIS_REPLY_ARRAY);
4879 size_t i;
4880 for (i = 0; i < reply->elements; i++) {
4881 redisReply *r = reply->element[i];
4882 assert(r->type == REDIS_REPLY_ARRAY && r->elements >= 3);
4883 int from, to;
4884 from = r->element[0]->integer;
4885 to = r->element[1]->integer;
4886 if (slot < from || slot > to) continue;
4887 redisReply *nr = r->element[2];
4888 assert(nr->type == REDIS_REPLY_ARRAY && nr->elements >= 2);
4889 char *name = NULL;
4890 if (nr->elements >= 3)
4891 name = nr->element[2]->str;
4892 if (name != NULL)
4893 owner = clusterManagerNodeByName(name);
4894 else {
4895 char *ip = nr->element[0]->str;
4896 assert(ip != NULL);
4897 int port = (int) nr->element[1]->integer;
4898 listIter li;
4899 listNode *ln;
4900 listRewind(cluster_manager.nodes, &li);
4901 while ((ln = listNext(&li)) != NULL) {
4902 clusterManagerNode *nd = ln->value;
4903 if (strcmp(nd->ip, ip) == 0 && port == nd->port) {
4904 owner = nd;
4905 break;
4906 }
4907 }
4908 }
4909 if (owner) break;
4910 }
4911 }
4912 if (reply) freeReplyObject(reply);
4913 return owner;
4914}
4915
4916/* Set slot status to "importing" or "migrating" */
4917static int clusterManagerSetSlot(clusterManagerNode *node1,
4918 clusterManagerNode *node2,
4919 int slot, const char *status, char **err) {
4920 redisReply *reply = CLUSTER_MANAGER_COMMAND(node1, "CLUSTER "
4921 "SETSLOT %d %s %s",
4922 slot, status,
4923 (char *) node2->name);
4924 if (err != NULL) *err = NULL;
4925 if (!reply) {
4926 if (err) *err = zstrdup("CLUSTER SETSLOT failed to run");
4927 return 0;
4928 }
4929 int success = 1;
4930 if (reply->type == REDIS_REPLY_ERROR) {
4931 success = 0;
4932 if (err != NULL) {
4933 *err = zmalloc((reply->len + 1) * sizeof(char));
4934 redis_strlcpy(*err, reply->str,(reply->len + 1));
4935 } else CLUSTER_MANAGER_PRINT_REPLY_ERROR(node1, reply->str);
4936 goto cleanup;
4937 }
4938cleanup:
4939 freeReplyObject(reply);
4940 return success;
4941}
4942
4943static int clusterManagerClearSlotStatus(clusterManagerNode *node, int slot) {
4944 redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
4945 "CLUSTER SETSLOT %d %s", slot, "STABLE");
4946 int success = clusterManagerCheckRedisReply(node, reply, NULL);
4947 if (reply) freeReplyObject(reply);
4948 return success;
4949}
4950
4951static int clusterManagerDelSlot(clusterManagerNode *node, int slot,
4952 int ignore_unassigned_err)
4953{
4954 redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
4955 "CLUSTER DELSLOTS %d", slot);
4956 char *err = NULL;
4957 int success = clusterManagerCheckRedisReply(node, reply, &err);
4958 if (!success && reply && reply->type == REDIS_REPLY_ERROR &&
4959 ignore_unassigned_err)
4960 {
4961 char *get_owner_err = NULL;
4962 clusterManagerNode *assigned_to =
4963 clusterManagerGetSlotOwner(node, slot, &get_owner_err);
4964 if (!assigned_to) {
4965 if (get_owner_err == NULL) success = 1;
4966 else {
4967 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, get_owner_err);
4968 zfree(get_owner_err);
4969 }
4970 }
4971 }
4972 if (!success && err != NULL) {
4973 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
4974 zfree(err);
4975 }
4976 if (reply) freeReplyObject(reply);
4977 return success;
4978}
4979
4980static int clusterManagerAddSlot(clusterManagerNode *node, int slot) {
4981 redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
4982 "CLUSTER ADDSLOTS %d", slot);
4983 int success = clusterManagerCheckRedisReply(node, reply, NULL);
4984 if (reply) freeReplyObject(reply);
4985 return success;
4986}
4987
4988static signed int clusterManagerCountKeysInSlot(clusterManagerNode *node,
4989 int slot)
4990{
4991 redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
4992 "CLUSTER COUNTKEYSINSLOT %d", slot);
4993 int count = -1;
4994 int success = clusterManagerCheckRedisReply(node, reply, NULL);
4995 if (success && reply->type == REDIS_REPLY_INTEGER) count = reply->integer;
4996 if (reply) freeReplyObject(reply);
4997 return count;
4998}
4999
5000static int clusterManagerBumpEpoch(clusterManagerNode *node) {
5001 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER BUMPEPOCH");
5002 int success = clusterManagerCheckRedisReply(node, reply, NULL);
5003 if (reply) freeReplyObject(reply);
5004 return success;
5005}
5006
5007/* Callback used by clusterManagerSetSlotOwner transaction. It should ignore
5008 * errors except for ADDSLOTS errors.
5009 * Return 1 if the error should be ignored. */
5010static int clusterManagerOnSetOwnerErr(redisReply *reply,
5011 clusterManagerNode *n, int bulk_idx)
5012{
5013 UNUSED(reply);
5014 UNUSED(n);
5015 /* Only raise error when ADDSLOTS fail (bulk_idx == 1). */
5016 return (bulk_idx != 1);
5017}
5018
5019static int clusterManagerSetSlotOwner(clusterManagerNode *owner,
5020 int slot,
5021 int do_clear)
5022{
5023 int success = clusterManagerStartTransaction(owner);
5024 if (!success) return 0;
5025 /* Ensure the slot is not already assigned. */
5026 clusterManagerDelSlot(owner, slot, 1);
5027 /* Add the slot and bump epoch. */
5028 clusterManagerAddSlot(owner, slot);
5029 if (do_clear) clusterManagerClearSlotStatus(owner, slot);
5030 clusterManagerBumpEpoch(owner);
5031 success = clusterManagerExecTransaction(owner, clusterManagerOnSetOwnerErr);
5032 return success;
5033}
5034
5035/* Get the hash for the values of the specified keys in *keys_reply for the
5036 * specified nodes *n1 and *n2, by calling DEBUG DIGEST-VALUE redis command
5037 * on both nodes. Every key with same name on both nodes but having different
5038 * values will be added to the *diffs list. Return 0 in case of reply
5039 * error. */
5040static int clusterManagerCompareKeysValues(clusterManagerNode *n1,
5041 clusterManagerNode *n2,
5042 redisReply *keys_reply,
5043 list *diffs)
5044{
5045 size_t i, argc = keys_reply->elements + 2;
5046 static const char *hash_zero = "0000000000000000000000000000000000000000";
5047 char **argv = zcalloc(argc * sizeof(char *));
5048 size_t *argv_len = zcalloc(argc * sizeof(size_t));
5049 argv[0] = "DEBUG";
5050 argv_len[0] = 5;
5051 argv[1] = "DIGEST-VALUE";
5052 argv_len[1] = 12;
5053 for (i = 0; i < keys_reply->elements; i++) {
5054 redisReply *entry = keys_reply->element[i];
5055 int idx = i + 2;
5056 argv[idx] = entry->str;
5057 argv_len[idx] = entry->len;
5058 }
5059 int success = 0;
5060 void *_reply1 = NULL, *_reply2 = NULL;
5061 redisReply *r1 = NULL, *r2 = NULL;
5062 redisAppendCommandArgv(n1->context,argc, (const char**)argv,argv_len);
5063 success = (redisGetReply(n1->context, &_reply1) == REDIS_OK);
5064 if (!success) goto cleanup;
5065 r1 = (redisReply *) _reply1;
5066 redisAppendCommandArgv(n2->context,argc, (const char**)argv,argv_len);
5067 success = (redisGetReply(n2->context, &_reply2) == REDIS_OK);
5068 if (!success) goto cleanup;
5069 r2 = (redisReply *) _reply2;
5070 success = (r1->type != REDIS_REPLY_ERROR && r2->type != REDIS_REPLY_ERROR);
5071 if (r1->type == REDIS_REPLY_ERROR) {
5072 CLUSTER_MANAGER_PRINT_REPLY_ERROR(n1, r1->str);
5073 success = 0;
5074 }
5075 if (r2->type == REDIS_REPLY_ERROR) {
5076 CLUSTER_MANAGER_PRINT_REPLY_ERROR(n2, r2->str);
5077 success = 0;
5078 }
5079 if (!success) goto cleanup;
5080 assert(keys_reply->elements == r1->elements &&
5081 keys_reply->elements == r2->elements);
5082 for (i = 0; i < keys_reply->elements; i++) {
5083 char *key = keys_reply->element[i]->str;
5084 char *hash1 = r1->element[i]->str;
5085 char *hash2 = r2->element[i]->str;
5086 /* Ignore keys that don't exist in both nodes. */
5087 if (strcmp(hash1, hash_zero) == 0 || strcmp(hash2, hash_zero) == 0)
5088 continue;
5089 if (strcmp(hash1, hash2) != 0) listAddNodeTail(diffs, key);
5090 }
5091cleanup:
5092 if (r1) freeReplyObject(r1);
5093 if (r2) freeReplyObject(r2);
5094 zfree(argv);
5095 zfree(argv_len);
5096 return success;
5097}
5098
5099/* Migrate keys taken from reply->elements. It returns the reply from the
5100 * MIGRATE command, or NULL if something goes wrong. If the argument 'dots'
5101 * is not NULL, a dot will be printed for every migrated key. */
5102static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
5103 clusterManagerNode *target,
5104 redisReply *reply,
5105 int replace, int timeout,
5106 char *dots)
5107{
5108 redisReply *migrate_reply = NULL;
5109 char **argv = NULL;
5110 size_t *argv_len = NULL;
5111 int c = (replace ? 8 : 7);
5112 if (config.conn_info.auth) c += 2;
5113 if (config.conn_info.user) c += 1;
5114 size_t argc = c + reply->elements;
5115 size_t i, offset = 6; // Keys Offset
5116 argv = zcalloc(argc * sizeof(char *));
5117 argv_len = zcalloc(argc * sizeof(size_t));
5118 char portstr[255];
5119 char timeoutstr[255];
5120 snprintf(portstr, 10, "%d", target->port);
5121 snprintf(timeoutstr, 10, "%d", timeout);
5122 argv[0] = "MIGRATE";
5123 argv_len[0] = 7;
5124 argv[1] = target->ip;
5125 argv_len[1] = strlen(target->ip);
5126 argv[2] = portstr;
5127 argv_len[2] = strlen(portstr);
5128 argv[3] = "";
5129 argv_len[3] = 0;
5130 argv[4] = "0";
5131 argv_len[4] = 1;
5132 argv[5] = timeoutstr;
5133 argv_len[5] = strlen(timeoutstr);
5134 if (replace) {
5135 argv[offset] = "REPLACE";
5136 argv_len[offset] = 7;
5137 offset++;
5138 }
5139 if (config.conn_info.auth) {
5140 if (config.conn_info.user) {
5141 argv[offset] = "AUTH2";
5142 argv_len[offset] = 5;
5143 offset++;
5144 argv[offset] = config.conn_info.user;
5145 argv_len[offset] = strlen(config.conn_info.user);
5146 offset++;
5147 argv[offset] = config.conn_info.auth;
5148 argv_len[offset] = strlen(config.conn_info.auth);
5149 offset++;
5150 } else {
5151 argv[offset] = "AUTH";
5152 argv_len[offset] = 4;
5153 offset++;
5154 argv[offset] = config.conn_info.auth;
5155 argv_len[offset] = strlen(config.conn_info.auth);
5156 offset++;
5157 }
5158 }
5159 argv[offset] = "KEYS";
5160 argv_len[offset] = 4;
5161 offset++;
5162 for (i = 0; i < reply->elements; i++) {
5163 redisReply *entry = reply->element[i];
5164 size_t idx = i + offset;
5165 assert(entry->type == REDIS_REPLY_STRING);
5166 argv[idx] = (char *) sdsnewlen(entry->str, entry->len);
5167 argv_len[idx] = entry->len;
5168 if (dots) dots[i] = '.';
5169 }
5170 if (dots) dots[reply->elements] = '\0';
5171 void *_reply = NULL;
5172 redisAppendCommandArgv(source->context,argc,
5173 (const char**)argv,argv_len);
5174 int success = (redisGetReply(source->context, &_reply) == REDIS_OK);
5175 for (i = 0; i < reply->elements; i++) sdsfree(argv[i + offset]);
5176 if (!success) goto cleanup;
5177 migrate_reply = (redisReply *) _reply;
5178cleanup:
5179 zfree(argv);
5180 zfree(argv_len);
5181 return migrate_reply;
5182}
5183
5184/* Migrate all keys in the given slot from source to target.*/
5185static int clusterManagerMigrateKeysInSlot(clusterManagerNode *source,
5186 clusterManagerNode *target,
5187 int slot, int timeout,
5188 int pipeline, int verbose,
5189 char **err)
5190{
5191 int success = 1;
5192 int do_fix = config.cluster_manager_command.flags &
5193 CLUSTER_MANAGER_CMD_FLAG_FIX;
5194 int do_replace = config.cluster_manager_command.flags &
5195 CLUSTER_MANAGER_CMD_FLAG_REPLACE;
5196 while (1) {
5197 char *dots = NULL;
5198 redisReply *reply = NULL, *migrate_reply = NULL;
5199 reply = CLUSTER_MANAGER_COMMAND(source, "CLUSTER "
5200 "GETKEYSINSLOT %d %d", slot,
5201 pipeline);
5202 success = (reply != NULL);
5203 if (!success) return 0;
5204 if (reply->type == REDIS_REPLY_ERROR) {
5205 success = 0;
5206 if (err != NULL) {
5207 *err = zmalloc((reply->len + 1) * sizeof(char));
5208 redis_strlcpy(*err, reply->str,(reply->len + 1));
5209 CLUSTER_MANAGER_PRINT_REPLY_ERROR(source, *err);
5210 }
5211 goto next;
5212 }
5213 assert(reply->type == REDIS_REPLY_ARRAY);
5214 size_t count = reply->elements;
5215 if (count == 0) {
5216 freeReplyObject(reply);
5217 break;
5218 }
5219 if (verbose) dots = zmalloc((count+1) * sizeof(char));
5220 /* Calling MIGRATE command. */
5221 migrate_reply = clusterManagerMigrateKeysInReply(source, target,
5222 reply, 0, timeout,
5223 dots);
5224 if (migrate_reply == NULL) goto next;
5225 if (migrate_reply->type == REDIS_REPLY_ERROR) {
5226 int is_busy = strstr(migrate_reply->str, "BUSYKEY") != NULL;
5227 int not_served = 0;
5228 if (!is_busy) {
5229 /* Check if the slot is unassigned (not served) in the
5230 * source node's configuration. */
5231 char *get_owner_err = NULL;
5232 clusterManagerNode *served_by =
5233 clusterManagerGetSlotOwner(source, slot, &get_owner_err);
5234 if (!served_by) {
5235 if (get_owner_err == NULL) not_served = 1;
5236 else {
5237 CLUSTER_MANAGER_PRINT_REPLY_ERROR(source,
5238 get_owner_err);
5239 zfree(get_owner_err);
5240 }
5241 }
5242 }
5243 /* Try to handle errors. */
5244 if (is_busy || not_served) {
5245 /* If the key's slot is not served, try to assign slot
5246 * to the target node. */
5247 if (do_fix && not_served) {
5248 clusterManagerLogWarn("*** Slot was not served, setting "
5249 "owner to node %s:%d.\n",
5250 target->ip, target->port);
5251 clusterManagerSetSlot(source, target, slot, "node", NULL);
5252 }
5253 /* If the key already exists in the target node (BUSYKEY),
5254 * check whether its value is the same in both nodes.
5255 * In case of equal values, retry migration with the
5256 * REPLACE option.
5257 * In case of different values:
5258 * - If the migration is requested by the fix command, stop
5259 * and warn the user.
5260 * - In other cases (ie. reshard), proceed only if the user
5261 * launched the command with the --cluster-replace option.*/
5262 if (is_busy) {
5263 clusterManagerLogWarn("\n*** Target key exists\n");
5264 if (!do_replace) {
5265 clusterManagerLogWarn("*** Checking key values on "
5266 "both nodes...\n");
5267 list *diffs = listCreate();
5268 success = clusterManagerCompareKeysValues(source,
5269 target, reply, diffs);
5270 if (!success) {
5271 clusterManagerLogErr("*** Value check failed!\n");
5272 listRelease(diffs);
5273 goto next;
5274 }
5275 if (listLength(diffs) > 0) {
5276 success = 0;
5277 clusterManagerLogErr(
5278 "*** Found %d key(s) in both source node and "
5279 "target node having different values.\n"
5280 " Source node: %s:%d\n"
5281 " Target node: %s:%d\n"
5282 " Keys(s):\n",
5283 listLength(diffs),
5284 source->ip, source->port,
5285 target->ip, target->port);
5286 listIter dli;
5287 listNode *dln;
5288 listRewind(diffs, &dli);
5289 while((dln = listNext(&dli)) != NULL) {
5290 char *k = dln->value;
5291 clusterManagerLogErr(" - %s\n", k);
5292 }
5293 clusterManagerLogErr("Please fix the above key(s) "
5294 "manually and try again "
5295 "or relaunch the command \n"
5296 "with --cluster-replace "
5297 "option to force key "
5298 "overriding.\n");
5299 listRelease(diffs);
5300 goto next;
5301 }
5302 listRelease(diffs);
5303 }
5304 clusterManagerLogWarn("*** Replacing target keys...\n");
5305 }
5306 freeReplyObject(migrate_reply);
5307 migrate_reply = clusterManagerMigrateKeysInReply(source,
5308 target,
5309 reply,
5310 is_busy,
5311 timeout,
5312 NULL);
5313 success = (migrate_reply != NULL &&
5314 migrate_reply->type != REDIS_REPLY_ERROR);
5315 } else success = 0;
5316 if (!success) {
5317 if (migrate_reply != NULL) {
5318 if (err) {
5319 *err = zmalloc((migrate_reply->len + 1) * sizeof(char));
5320 redis_strlcpy(*err, migrate_reply->str, (migrate_reply->len + 1));
5321 }
5322 printf("\n");
5323 CLUSTER_MANAGER_PRINT_REPLY_ERROR(source,
5324 migrate_reply->str);
5325 }
5326 goto next;
5327 }
5328 }
5329 if (verbose) {
5330 printf("%s", dots);
5331 fflush(stdout);
5332 }
5333next:
5334 if (reply != NULL) freeReplyObject(reply);
5335 if (migrate_reply != NULL) freeReplyObject(migrate_reply);
5336 if (dots) zfree(dots);
5337 if (!success) break;
5338 }
5339 return success;
5340}
5341
5342/* Move slots between source and target nodes using MIGRATE.
5343 *
5344 * Options:
5345 * CLUSTER_MANAGER_OPT_VERBOSE -- Print a dot for every moved key.
5346 * CLUSTER_MANAGER_OPT_COLD -- Move keys without opening slots /
5347 * reconfiguring the nodes.
5348 * CLUSTER_MANAGER_OPT_UPDATE -- Update node->slots for source/target nodes.
5349 * CLUSTER_MANAGER_OPT_QUIET -- Don't print info messages.
5350*/
5351static int clusterManagerMoveSlot(clusterManagerNode *source,
5352 clusterManagerNode *target,
5353 int slot, int opts, char**err)
5354{
5355 if (!(opts & CLUSTER_MANAGER_OPT_QUIET)) {
5356 printf("Moving slot %d from %s:%d to %s:%d: ", slot, source->ip,
5357 source->port, target->ip, target->port);
5358 fflush(stdout);
5359 }
5360 if (err != NULL) *err = NULL;
5361 int pipeline = config.cluster_manager_command.pipeline,
5362 timeout = config.cluster_manager_command.timeout,
5363 print_dots = (opts & CLUSTER_MANAGER_OPT_VERBOSE),
5364 option_cold = (opts & CLUSTER_MANAGER_OPT_COLD),
5365 success = 1;
5366 if (!option_cold) {
5367 success = clusterManagerSetSlot(target, source, slot,
5368 "importing", err);
5369 if (!success) return 0;
5370 success = clusterManagerSetSlot(source, target, slot,
5371 "migrating", err);
5372 if (!success) return 0;
5373 }
5374 success = clusterManagerMigrateKeysInSlot(source, target, slot, timeout,
5375 pipeline, print_dots, err);
5376 if (!(opts & CLUSTER_MANAGER_OPT_QUIET)) printf("\n");
5377 if (!success) return 0;
5378 if (!option_cold) {
5379 /* Set the new node as the owner of the slot in all the known nodes.
5380 *
5381 * We inform the target node first. It will propagate the information to
5382 * the rest of the cluster.
5383 *
5384 * If we inform any other node first, it can happen that the target node
5385 * crashes before it is set as the new owner and then the slot is left
5386 * without an owner which results in redirect loops. See issue #7116. */
5387 success = clusterManagerSetSlot(target, target, slot, "node", err);
5388 if (!success) return 0;
5389
5390 /* Inform the source node. If the source node has just lost its last
5391 * slot and the target node has already informed the source node, the
5392 * source node has turned itself into a replica. This is not an error in
5393 * this scenario so we ignore it. See issue #9223. */
5394 success = clusterManagerSetSlot(source, target, slot, "node", err);
5395 const char *acceptable = "ERR Please use SETSLOT only with masters.";
5396 if (!success && err && !strncmp(*err, acceptable, strlen(acceptable))) {
5397 zfree(*err);
5398 *err = NULL;
5399 } else if (!success && err) {
5400 return 0;
5401 }
5402
5403 /* We also inform the other nodes to avoid redirects in case the target
5404 * node is slow to propagate the change to the entire cluster. */
5405 listIter li;
5406 listNode *ln;
5407 listRewind(cluster_manager.nodes, &li);
5408 while ((ln = listNext(&li)) != NULL) {
5409 clusterManagerNode *n = ln->value;
5410 if (n == target || n == source) continue; /* already done */
5411 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
5412 success = clusterManagerSetSlot(n, target, slot, "node", err);
5413 if (!success) return 0;
5414 }
5415 }
5416 /* Update the node logical config */
5417 if (opts & CLUSTER_MANAGER_OPT_UPDATE) {
5418 source->slots[slot] = 0;
5419 target->slots[slot] = 1;
5420 }
5421 return 1;
5422}
5423
5424/* Flush the dirty node configuration by calling replicate for slaves or
5425 * adding the slots defined in the masters. */
5426static int clusterManagerFlushNodeConfig(clusterManagerNode *node, char **err) {
5427 if (!node->dirty) return 0;
5428 redisReply *reply = NULL;
5429 int is_err = 0, success = 1;
5430 if (err != NULL) *err = NULL;
5431 if (node->replicate != NULL) {
5432 reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER REPLICATE %s",
5433 node->replicate);
5434 if (reply == NULL || (is_err = (reply->type == REDIS_REPLY_ERROR))) {
5435 if (is_err && err != NULL) {
5436 *err = zmalloc((reply->len + 1) * sizeof(char));
5437 redis_strlcpy(*err, reply->str, (reply->len + 1));
5438 }
5439 success = 0;
5440 /* If the cluster did not already joined it is possible that
5441 * the slave does not know the master node yet. So on errors
5442 * we return ASAP leaving the dirty flag set, to flush the
5443 * config later. */
5444 goto cleanup;
5445 }
5446 } else {
5447 int added = clusterManagerAddSlots(node, err);
5448 if (!added || *err != NULL) success = 0;
5449 }
5450 node->dirty = 0;
5451cleanup:
5452 if (reply != NULL) freeReplyObject(reply);
5453 return success;
5454}
5455
5456/* Wait until the cluster configuration is consistent. */
5457static void clusterManagerWaitForClusterJoin(void) {
5458 printf("Waiting for the cluster to join\n");
5459 int counter = 0,
5460 check_after = CLUSTER_JOIN_CHECK_AFTER +
5461 (int)(listLength(cluster_manager.nodes) * 0.15f);
5462 while(!clusterManagerIsConfigConsistent()) {
5463 printf(".");
5464 fflush(stdout);
5465 sleep(1);
5466 if (++counter > check_after) {
5467 dict *status = clusterManagerGetLinkStatus();
5468 if (status != NULL && dictSize(status) > 0) {
5469 printf("\n");
5470 clusterManagerLogErr("Warning: %d node(s) may "
5471 "be unreachable\n", dictSize(status));
5472 dictIterator iter;
5473 dictEntry *entry;
5474 dictInitIterator(&iter, status);
5475 while ((entry = dictNext(&iter)) != NULL) {
5476 sds nodeaddr = (sds) dictGetKey(entry);
5477 char *node_ip = NULL;
5478 int node_port = 0, node_bus_port = 0;
5479 list *from = (list *) dictGetVal(entry);
5480 if (parseClusterNodeAddress(nodeaddr, &node_ip,
5481 &node_port, &node_bus_port) && node_bus_port) {
5482 clusterManagerLogErr(" - The port %d of node %s may "
5483 "be unreachable from:\n",
5484 node_bus_port, node_ip);
5485 } else {
5486 clusterManagerLogErr(" - Node %s may be unreachable "
5487 "from:\n", nodeaddr);
5488 }
5489 listIter li;
5490 listNode *ln;
5491 listRewind(from, &li);
5492 while ((ln = listNext(&li)) != NULL) {
5493 sds from_addr = ln->value;
5494 clusterManagerLogErr(" %s\n", from_addr);
5495 sdsfree(from_addr);
5496 }
5497 clusterManagerLogErr("Cluster bus ports must be reachable "
5498 "by every node.\nRemember that "
5499 "cluster bus ports are different "
5500 "from standard instance ports.\n");
5501 listEmpty(from);
5502 }
5503 dictResetIterator(&iter);
5504 }
5505 if (status != NULL) dictRelease(status);
5506 counter = 0;
5507 }
5508 }
5509 printf("\n");
5510}
5511
5512/* Load node's cluster configuration by calling "CLUSTER NODES" command.
5513 * Node's configuration (name, replicate, slots, ...) is then updated.
5514 * If CLUSTER_MANAGER_OPT_GETFRIENDS flag is set into 'opts' argument,
5515 * and node already knows other nodes, the node's friends list is populated
5516 * with the other nodes info. */
5517static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts,
5518 char **err)
5519{
5520 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER NODES");
5521 int success = 1;
5522 *err = NULL;
5523 if (!clusterManagerCheckRedisReply(node, reply, err)) {
5524 success = 0;
5525 goto cleanup;
5526 }
5527 int getfriends = (opts & CLUSTER_MANAGER_OPT_GETFRIENDS);
5528 char *lines = reply->str, *p, *line;
5529 while ((p = strstr(lines, "\n")) != NULL) {
5530 *p = '\0';
5531 line = lines;
5532 lines = p + 1;
5533 char *name = NULL, *addr = NULL, *flags = NULL, *master_id = NULL,
5534 *ping_sent = NULL, *ping_recv = NULL, *config_epoch = NULL,
5535 *link_status = NULL;
5536 UNUSED(link_status);
5537 int i = 0;
5538 while ((p = strchr(line, ' ')) != NULL) {
5539 *p = '\0';
5540 char *token = line;
5541 line = p + 1;
5542 switch(i++){
5543 case 0: name = token; break;
5544 case 1: addr = token; break;
5545 case 2: flags = token; break;
5546 case 3: master_id = token; break;
5547 case 4: ping_sent = token; break;
5548 case 5: ping_recv = token; break;
5549 case 6: config_epoch = token; break;
5550 case 7: link_status = token; break;
5551 }
5552 if (i == 8) break; // Slots
5553 }
5554 if (!flags) {
5555 success = 0;
5556 goto cleanup;
5557 }
5558
5559 char *ip = NULL;
5560 int port = 0, bus_port = 0;
5561 if (addr == NULL || !parseClusterNodeAddress(addr, &ip, &port, &bus_port)) {
5562 fprintf(stderr, "Error: invalid CLUSTER NODES reply\n");
5563 success = 0;
5564 goto cleanup;
5565 }
5566
5567 int myself = (strstr(flags, "myself") != NULL);
5568 clusterManagerNode *currentNode = NULL;
5569 if (myself) {
5570 /* bus-port could be wrong, correct it here, see clusterManagerNewNode. */
5571 node->bus_port = bus_port;
5572 node->flags |= CLUSTER_MANAGER_FLAG_MYSELF;
5573 currentNode = node;
5574 clusterManagerNodeResetSlots(node);
5575 if (i == 8) {
5576 int remaining = strlen(line);
5577 while (remaining > 0) {
5578 p = strchr(line, ' ');
5579 if (p == NULL) p = line + remaining;
5580 remaining -= (p - line);
5581
5582 char *slotsdef = line;
5583 *p = '\0';
5584 if (remaining) {
5585 line = p + 1;
5586 remaining--;
5587 } else line = p;
5588 char *dash = NULL;
5589 if (slotsdef[0] == '[') {
5590 slotsdef++;
5591 if ((p = strstr(slotsdef, "->-"))) { // Migrating
5592 *p = '\0';
5593 p += 3;
5594 char *closing_bracket = strchr(p, ']');
5595 if (closing_bracket) *closing_bracket = '\0';
5596 sds slot = sdsnew(slotsdef);
5597 sds dst = sdsnew(p);
5598 node->migrating_count += 2;
5599 node->migrating = zrealloc(node->migrating,
5600 (node->migrating_count * sizeof(sds)));
5601 node->migrating[node->migrating_count - 2] =
5602 slot;
5603 node->migrating[node->migrating_count - 1] =
5604 dst;
5605 } else if ((p = strstr(slotsdef, "-<-"))) {//Importing
5606 *p = '\0';
5607 p += 3;
5608 char *closing_bracket = strchr(p, ']');
5609 if (closing_bracket) *closing_bracket = '\0';
5610 sds slot = sdsnew(slotsdef);
5611 sds src = sdsnew(p);
5612 node->importing_count += 2;
5613 node->importing = zrealloc(node->importing,
5614 (node->importing_count * sizeof(sds)));
5615 node->importing[node->importing_count - 2] =
5616 slot;
5617 node->importing[node->importing_count - 1] =
5618 src;
5619 }
5620 } else if ((dash = strchr(slotsdef, '-')) != NULL) {
5621 p = dash;
5622 int start, stop;
5623 *p = '\0';
5624 start = atoi(slotsdef);
5625 stop = atoi(p + 1);
5626 node->slots_count += (stop - (start - 1));
5627 while (start <= stop) node->slots[start++] = 1;
5628 } else if (p > slotsdef) {
5629 node->slots[atoi(slotsdef)] = 1;
5630 node->slots_count++;
5631 }
5632 }
5633 }
5634 node->dirty = 0;
5635 } else if (!getfriends) {
5636 if (!(node->flags & CLUSTER_MANAGER_FLAG_MYSELF)) continue;
5637 else break;
5638 } else {
5639 currentNode = clusterManagerNewNode(sdsnew(ip), port, bus_port);
5640 currentNode->flags |= CLUSTER_MANAGER_FLAG_FRIEND;
5641 if (node->friends == NULL) node->friends = listCreate();
5642 listAddNodeTail(node->friends, currentNode);
5643 }
5644 if (name != NULL) {
5645 if (currentNode->name) sdsfree(currentNode->name);
5646 currentNode->name = sdsnew(name);
5647 }
5648 if (currentNode->flags_str != NULL)
5649 freeClusterManagerNodeFlags(currentNode->flags_str);
5650 currentNode->flags_str = listCreate();
5651 int flag_len;
5652 while ((flag_len = strlen(flags)) > 0) {
5653 sds flag = NULL;
5654 char *fp = strchr(flags, ',');
5655 if (fp) {
5656 *fp = '\0';
5657 flag = sdsnew(flags);
5658 flags = fp + 1;
5659 } else {
5660 flag = sdsnew(flags);
5661 flags += flag_len;
5662 }
5663 if (strcmp(flag, "noaddr") == 0)
5664 currentNode->flags |= CLUSTER_MANAGER_FLAG_NOADDR;
5665 else if (strcmp(flag, "disconnected") == 0)
5666 currentNode->flags |= CLUSTER_MANAGER_FLAG_DISCONNECT;
5667 else if (strcmp(flag, "fail") == 0)
5668 currentNode->flags |= CLUSTER_MANAGER_FLAG_FAIL;
5669 else if (strcmp(flag, "slave") == 0) {
5670 currentNode->flags |= CLUSTER_MANAGER_FLAG_SLAVE;
5671 if (master_id != NULL) {
5672 if (currentNode->replicate) sdsfree(currentNode->replicate);
5673 currentNode->replicate = sdsnew(master_id);
5674 }
5675 }
5676 listAddNodeTail(currentNode->flags_str, flag);
5677 }
5678 if (config_epoch != NULL)
5679 currentNode->current_epoch = atoll(config_epoch);
5680 if (ping_sent != NULL) currentNode->ping_sent = atoll(ping_sent);
5681 if (ping_recv != NULL) currentNode->ping_recv = atoll(ping_recv);
5682 if (!getfriends && myself) break;
5683 }
5684cleanup:
5685 if (reply) freeReplyObject(reply);
5686 return success;
5687}
5688
5689/* Retrieves info about the cluster using argument 'node' as the starting
5690 * point. All nodes will be loaded inside the cluster_manager.nodes list.
5691 * Warning: if something goes wrong, it will free the starting node before
5692 * returning 0. */
5693static int clusterManagerLoadInfoFromNode(clusterManagerNode *node) {
5694 if (node->context == NULL && !clusterManagerNodeConnect(node)) {
5695 freeClusterManagerNode(node);
5696 return 0;
5697 }
5698 char *e = NULL;
5699 if (!clusterManagerNodeIsCluster(node, &e)) {
5700 clusterManagerPrintNotClusterNodeError(node, e);
5701 if (e) zfree(e);
5702 freeClusterManagerNode(node);
5703 return 0;
5704 }
5705 e = NULL;
5706 if (!clusterManagerNodeLoadInfo(node, CLUSTER_MANAGER_OPT_GETFRIENDS, &e)) {
5707 if (e) {
5708 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, e);
5709 zfree(e);
5710 }
5711 freeClusterManagerNode(node);
5712 return 0;
5713 }
5714 listIter li;
5715 listNode *ln;
5716 if (cluster_manager.nodes != NULL) {
5717 listRewind(cluster_manager.nodes, &li);
5718 while ((ln = listNext(&li)) != NULL)
5719 freeClusterManagerNode((clusterManagerNode *) ln->value);
5720 listRelease(cluster_manager.nodes);
5721 }
5722 cluster_manager.nodes = listCreate();
5723 listAddNodeTail(cluster_manager.nodes, node);
5724 if (node->friends != NULL) {
5725 listRewind(node->friends, &li);
5726 while ((ln = listNext(&li)) != NULL) {
5727 clusterManagerNode *friend = ln->value;
5728 if (!friend->ip || !friend->port) goto invalid_friend;
5729 if (!friend->context && !clusterManagerNodeConnect(friend))
5730 goto invalid_friend;
5731 e = NULL;
5732 if (clusterManagerNodeLoadInfo(friend, 0, &e)) {
5733 if (friend->flags & (CLUSTER_MANAGER_FLAG_NOADDR |
5734 CLUSTER_MANAGER_FLAG_DISCONNECT |
5735 CLUSTER_MANAGER_FLAG_FAIL))
5736 {
5737 goto invalid_friend;
5738 }
5739 listAddNodeTail(cluster_manager.nodes, friend);
5740 } else {
5741 clusterManagerLogErr("[ERR] Unable to load info for "
5742 "node %s:%d\n",
5743 friend->ip, friend->port);
5744 goto invalid_friend;
5745 }
5746 continue;
5747invalid_friend:
5748 if (!(friend->flags & CLUSTER_MANAGER_FLAG_SLAVE))
5749 cluster_manager.unreachable_masters++;
5750 freeClusterManagerNode(friend);
5751 }
5752 listRelease(node->friends);
5753 node->friends = NULL;
5754 }
5755 // Count replicas for each node
5756 listRewind(cluster_manager.nodes, &li);
5757 while ((ln = listNext(&li)) != NULL) {
5758 clusterManagerNode *n = ln->value;
5759 if (n->replicate != NULL) {
5760 clusterManagerNode *master = clusterManagerNodeByName(n->replicate);
5761 if (master == NULL) {
5762 clusterManagerLogWarn("*** WARNING: %s:%d claims to be "
5763 "slave of unknown node ID %s.\n",
5764 n->ip, n->port, n->replicate);
5765 } else master->replicas_count++;
5766 }
5767 }
5768 return 1;
5769}
5770
5771/* Compare functions used by various sorting operations. */
5772int clusterManagerSlotCompare(const void *slot1, const void *slot2) {
5773 const char **i1 = (const char **)slot1;
5774 const char **i2 = (const char **)slot2;
5775 return strcmp(*i1, *i2);
5776}
5777
5778int clusterManagerSlotCountCompareDesc(const void *n1, const void *n2) {
5779 clusterManagerNode *node1 = *((clusterManagerNode **) n1);
5780 clusterManagerNode *node2 = *((clusterManagerNode **) n2);
5781 return node2->slots_count - node1->slots_count;
5782}
5783
5784int clusterManagerCompareNodeBalance(const void *n1, const void *n2) {
5785 clusterManagerNode *node1 = *((clusterManagerNode **) n1);
5786 clusterManagerNode *node2 = *((clusterManagerNode **) n2);
5787 return node1->balance - node2->balance;
5788}
5789
5790static sds clusterManagerGetConfigSignature(clusterManagerNode *node) {
5791 sds signature = NULL;
5792 int node_count = 0, i = 0, name_len = 0;
5793 char **node_configs = NULL;
5794 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER NODES");
5795 if (reply == NULL || reply->type == REDIS_REPLY_ERROR)
5796 goto cleanup;
5797 char *lines = reply->str, *p, *line;
5798 while ((p = strstr(lines, "\n")) != NULL) {
5799 i = 0;
5800 *p = '\0';
5801 line = lines;
5802 lines = p + 1;
5803 char *nodename = NULL;
5804 int tot_size = 0;
5805 while ((p = strchr(line, ' ')) != NULL) {
5806 *p = '\0';
5807 char *token = line;
5808 line = p + 1;
5809 if (i == 0) {
5810 nodename = token;
5811 tot_size = (p - token);
5812 name_len = tot_size++; // Make room for ':' in tot_size
5813 }
5814 if (++i == 8) break;
5815 }
5816 if (i != 8) continue;
5817 if (nodename == NULL) continue;
5818 int remaining = strlen(line);
5819 if (remaining == 0) continue;
5820 char **slots = NULL;
5821 int c = 0;
5822 while (remaining > 0) {
5823 p = strchr(line, ' ');
5824 if (p == NULL) p = line + remaining;
5825 int size = (p - line);
5826 remaining -= size;
5827 tot_size += size;
5828 char *slotsdef = line;
5829 *p = '\0';
5830 if (remaining) {
5831 line = p + 1;
5832 remaining--;
5833 } else line = p;
5834 if (slotsdef[0] != '[') {
5835 c++;
5836 slots = zrealloc(slots, (c * sizeof(char *)));
5837 slots[c - 1] = slotsdef;
5838 }
5839 }
5840 if (c > 0) {
5841 if (c > 1)
5842 qsort(slots, c, sizeof(char *), clusterManagerSlotCompare);
5843 node_count++;
5844 node_configs =
5845 zrealloc(node_configs, (node_count * sizeof(char *)));
5846 /* Make room for '|' separators. */
5847 tot_size += (sizeof(char) * (c - 1));
5848 char *cfg = zmalloc((sizeof(char) * tot_size) + 1);
5849 memcpy(cfg, nodename, name_len);
5850 char *sp = cfg + name_len;
5851 *(sp++) = ':';
5852 for (i = 0; i < c; i++) {
5853 if (i > 0) *(sp++) = ',';
5854 int slen = strlen(slots[i]);
5855 memcpy(sp, slots[i], slen);
5856 sp += slen;
5857 }
5858 *(sp++) = '\0';
5859 node_configs[node_count - 1] = cfg;
5860 }
5861 zfree(slots);
5862 }
5863 if (node_count > 0) {
5864 if (node_count > 1) {
5865 qsort(node_configs, node_count, sizeof(char *),
5866 clusterManagerSlotCompare);
5867 }
5868 signature = sdsempty();
5869 for (i = 0; i < node_count; i++) {
5870 if (i > 0) signature = sdscatprintf(signature, "%c", '|');
5871 signature = sdscatfmt(signature, "%s", node_configs[i]);
5872 }
5873 }
5874cleanup:
5875 if (reply != NULL) freeReplyObject(reply);
5876 if (node_configs != NULL) {
5877 for (i = 0; i < node_count; i++) zfree(node_configs[i]);
5878 zfree(node_configs);
5879 }
5880 return signature;
5881}
5882
5883static int clusterManagerIsConfigConsistent(void) {
5884 if (cluster_manager.nodes == NULL) return 0;
5885 int consistent = (listLength(cluster_manager.nodes) <= 1);
5886 // If the Cluster has only one node, it's always consistent
5887 if (consistent) return 1;
5888 sds first_cfg = NULL;
5889 listIter li;
5890 listNode *ln;
5891 listRewind(cluster_manager.nodes, &li);
5892 while ((ln = listNext(&li)) != NULL) {
5893 clusterManagerNode *node = ln->value;
5894 sds cfg = clusterManagerGetConfigSignature(node);
5895 if (cfg == NULL) {
5896 consistent = 0;
5897 break;
5898 }
5899 if (first_cfg == NULL) first_cfg = cfg;
5900 else {
5901 consistent = !sdscmp(first_cfg, cfg);
5902 sdsfree(cfg);
5903 if (!consistent) break;
5904 }
5905 }
5906 if (first_cfg != NULL) sdsfree(first_cfg);
5907 return consistent;
5908}
5909
5910static list *clusterManagerGetDisconnectedLinks(clusterManagerNode *node) {
5911 list *links = NULL;
5912 redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER NODES");
5913 if (!clusterManagerCheckRedisReply(node, reply, NULL)) goto cleanup;
5914 links = listCreate();
5915 char *lines = reply->str, *p, *line;
5916 while ((p = strstr(lines, "\n")) != NULL) {
5917 int i = 0;
5918 *p = '\0';
5919 line = lines;
5920 lines = p + 1;
5921 char *nodename = NULL, *addr = NULL, *flags = NULL, *link_status = NULL;
5922 while ((p = strchr(line, ' ')) != NULL) {
5923 *p = '\0';
5924 char *token = line;
5925 line = p + 1;
5926 if (i == 0) nodename = token;
5927 else if (i == 1) addr = token;
5928 else if (i == 2) flags = token;
5929 else if (i == 7) link_status = token;
5930 else if (i == 8) break;
5931 i++;
5932 }
5933 if (i == 7) link_status = line;
5934 if (nodename == NULL || addr == NULL || flags == NULL ||
5935 link_status == NULL) continue;
5936 if (strstr(flags, "myself") != NULL) continue;
5937 int disconnected = ((strstr(flags, "disconnected") != NULL) ||
5938 (strstr(link_status, "disconnected")));
5939 int handshaking = (strstr(flags, "handshake") != NULL);
5940 if (disconnected || handshaking) {
5941 clusterManagerLink *link = zmalloc(sizeof(*link));
5942 link->node_name = sdsnew(nodename);
5943 link->node_addr = sdsnew(addr);
5944 link->connected = 0;
5945 link->handshaking = handshaking;
5946 listAddNodeTail(links, link);
5947 }
5948 }
5949cleanup:
5950 if (reply != NULL) freeReplyObject(reply);
5951 return links;
5952}
5953
5954/* Check for disconnected cluster links. It returns a dict whose keys
5955 * are the unreachable node addresses and the values are lists of
5956 * node addresses that cannot reach the unreachable node. */
5957static dict *clusterManagerGetLinkStatus(void) {
5958 if (cluster_manager.nodes == NULL) return NULL;
5959 dict *status = dictCreate(&clusterManagerLinkDictType);
5960 listIter li;
5961 listNode *ln;
5962 listRewind(cluster_manager.nodes, &li);
5963 while ((ln = listNext(&li)) != NULL) {
5964 clusterManagerNode *node = ln->value;
5965 list *links = clusterManagerGetDisconnectedLinks(node);
5966 if (links) {
5967 listIter lli;
5968 listNode *lln;
5969 listRewind(links, &lli);
5970 while ((lln = listNext(&lli)) != NULL) {
5971 clusterManagerLink *link = lln->value;
5972 list *from = NULL;
5973 dictEntry *entry = dictFind(status, link->node_addr);
5974 if (entry) from = dictGetVal(entry);
5975 else {
5976 from = listCreate();
5977 dictAdd(status, sdsdup(link->node_addr), from);
5978 }
5979 sds myaddr = sdsempty();
5980 myaddr = sdscatfmt(myaddr, "%s:%u", node->ip, node->port);
5981 listAddNodeTail(from, myaddr);
5982 sdsfree(link->node_name);
5983 sdsfree(link->node_addr);
5984 zfree(link);
5985 }
5986 listRelease(links);
5987 }
5988 }
5989 return status;
5990}
5991
5992/* Add the error string to cluster_manager.errors and print it. */
5993static void clusterManagerOnError(sds err) {
5994 if (cluster_manager.errors == NULL)
5995 cluster_manager.errors = listCreate();
5996 listAddNodeTail(cluster_manager.errors, err);
5997 clusterManagerLogErr("%s\n", (char *) err);
5998}
5999
6000/* Check the slots coverage of the cluster. The 'all_slots' argument must be
6001 * and array of 16384 bytes. Every covered slot will be set to 1 in the
6002 * 'all_slots' array. The function returns the total number if covered slots.*/
6003static int clusterManagerGetCoveredSlots(char *all_slots) {
6004 if (cluster_manager.nodes == NULL) return 0;
6005 listIter li;
6006 listNode *ln;
6007 listRewind(cluster_manager.nodes, &li);
6008 int totslots = 0, i;
6009 while ((ln = listNext(&li)) != NULL) {
6010 clusterManagerNode *node = ln->value;
6011 for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
6012 if (node->slots[i] && !all_slots[i]) {
6013 all_slots[i] = 1;
6014 totslots++;
6015 }
6016 }
6017 }
6018 return totslots;
6019}
6020
6021static void clusterManagerPrintSlotsList(list *slots) {
6022 clusterManagerNode n = {0};
6023 listIter li;
6024 listNode *ln;
6025 listRewind(slots, &li);
6026 while ((ln = listNext(&li)) != NULL) {
6027 int slot = atoi(ln->value);
6028 if (slot >= 0 && slot < CLUSTER_MANAGER_SLOTS)
6029 n.slots[slot] = 1;
6030 }
6031 sds nodeslist = clusterManagerNodeSlotsString(&n);
6032 printf("%s\n", nodeslist);
6033 sdsfree(nodeslist);
6034}
6035
6036/* Return the node, among 'nodes' with the greatest number of keys
6037 * in the specified slot. */
6038static clusterManagerNode * clusterManagerGetNodeWithMostKeysInSlot(list *nodes,
6039 int slot,
6040 char **err)
6041{
6042 clusterManagerNode *node = NULL;
6043 int numkeys = 0;
6044 listIter li;
6045 listNode *ln;
6046 listRewind(nodes, &li);
6047 if (err) *err = NULL;
6048 while ((ln = listNext(&li)) != NULL) {
6049 clusterManagerNode *n = ln->value;
6050 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
6051 continue;
6052 redisReply *r =
6053 CLUSTER_MANAGER_COMMAND(n, "CLUSTER COUNTKEYSINSLOT %d", slot);
6054 int success = clusterManagerCheckRedisReply(n, r, err);
6055 if (success) {
6056 if (r->integer > numkeys || node == NULL) {
6057 numkeys = r->integer;
6058 node = n;
6059 }
6060 }
6061 if (r != NULL) freeReplyObject(r);
6062 /* If the reply contains errors */
6063 if (!success) {
6064 if (err != NULL && *err != NULL)
6065 CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, err);
6066 node = NULL;
6067 break;
6068 }
6069 }
6070 return node;
6071}
6072
6073/* This function returns the master that has the least number of replicas
6074 * in the cluster. If there are multiple masters with the same smaller
6075 * number of replicas, one at random is returned. */
6076
6077static clusterManagerNode *clusterManagerNodeWithLeastReplicas(void) {
6078 clusterManagerNode *node = NULL;
6079 int lowest_count = 0;
6080 listIter li;
6081 listNode *ln;
6082 listRewind(cluster_manager.nodes, &li);
6083 while ((ln = listNext(&li)) != NULL) {
6084 clusterManagerNode *n = ln->value;
6085 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
6086 if (node == NULL || n->replicas_count < lowest_count) {
6087 node = n;
6088 lowest_count = n->replicas_count;
6089 }
6090 }
6091 return node;
6092}
6093
6094/* This function returns a random master node, return NULL if none */
6095
6096static clusterManagerNode *clusterManagerNodeMasterRandom(void) {
6097 int master_count = 0;
6098 int idx;
6099 listIter li;
6100 listNode *ln;
6101 listRewind(cluster_manager.nodes, &li);
6102 while ((ln = listNext(&li)) != NULL) {
6103 clusterManagerNode *n = ln->value;
6104 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
6105 master_count++;
6106 }
6107
6108 assert(master_count > 0);
6109 srand(time(NULL));
6110 idx = rand() % master_count;
6111 listRewind(cluster_manager.nodes, &li);
6112 while ((ln = listNext(&li)) != NULL) {
6113 clusterManagerNode *n = ln->value;
6114 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
6115 if (!idx--) {
6116 return n;
6117 }
6118 }
6119 /* Can not be reached */
6120 assert(0);
6121 return NULL;
6122}
6123
6124static int clusterManagerFixSlotsCoverage(char *all_slots) {
6125 int force_fix = config.cluster_manager_command.flags &
6126 CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
6127
6128 if (cluster_manager.unreachable_masters > 0 && !force_fix) {
6129 clusterManagerLogWarn("*** Fixing slots coverage with %d unreachable masters is dangerous: redis-cli will assume that slots about masters that are not reachable are not covered, and will try to reassign them to the reachable nodes. This can cause data loss and is rarely what you want to do. If you really want to proceed use the --cluster-fix-with-unreachable-masters option.\n", cluster_manager.unreachable_masters);
6130 exit(1);
6131 }
6132
6133 int i, fixed = 0;
6134 list *none = NULL, *single = NULL, *multi = NULL;
6135 clusterManagerLogInfo(">>> Fixing slots coverage...\n");
6136 for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
6137 int covered = all_slots[i];
6138 if (!covered) {
6139 sds slot = sdsfromlonglong((long long) i);
6140 list *slot_nodes = listCreate();
6141 sds slot_nodes_str = sdsempty();
6142 listIter li;
6143 listNode *ln;
6144 listRewind(cluster_manager.nodes, &li);
6145 while ((ln = listNext(&li)) != NULL) {
6146 clusterManagerNode *n = ln->value;
6147 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
6148 continue;
6149 redisReply *reply = CLUSTER_MANAGER_COMMAND(n,
6150 "CLUSTER GETKEYSINSLOT %d %d", i, 1);
6151 if (!clusterManagerCheckRedisReply(n, reply, NULL)) {
6152 fixed = -1;
6153 if (reply) freeReplyObject(reply);
6154 if (slot_nodes) listRelease(slot_nodes);
6155 goto cleanup;
6156 }
6157 assert(reply->type == REDIS_REPLY_ARRAY);
6158 if (reply->elements > 0) {
6159 listAddNodeTail(slot_nodes, n);
6160 if (listLength(slot_nodes) > 1)
6161 slot_nodes_str = sdscat(slot_nodes_str, ", ");
6162 slot_nodes_str = sdscatfmt(slot_nodes_str,
6163 "%s:%u", n->ip, n->port);
6164 }
6165 freeReplyObject(reply);
6166 }
6167 sdsfree(slot_nodes_str);
6168 dictAdd(clusterManagerUncoveredSlots, slot, slot_nodes);
6169 }
6170 }
6171
6172 /* For every slot, take action depending on the actual condition:
6173 * 1) No node has keys for this slot.
6174 * 2) A single node has keys for this slot.
6175 * 3) Multiple nodes have keys for this slot. */
6176 none = listCreate();
6177 single = listCreate();
6178 multi = listCreate();
6179 dictIterator iter;
6180 dictEntry *entry;
6181
6182 dictInitIterator(&iter, clusterManagerUncoveredSlots);
6183 while ((entry = dictNext(&iter)) != NULL) {
6184 sds slot = (sds) dictGetKey(entry);
6185 list *nodes = (list *) dictGetVal(entry);
6186 switch (listLength(nodes)){
6187 case 0: listAddNodeTail(none, slot); break;
6188 case 1: listAddNodeTail(single, slot); break;
6189 default: listAddNodeTail(multi, slot); break;
6190 }
6191 }
6192 dictResetIterator(&iter);
6193
6194 /* we want explicit manual confirmation from users for all the fix cases */
6195 int ignore_force = 1;
6196
6197 /* Handle case "1": keys in no node. */
6198 if (listLength(none) > 0) {
6199 printf("The following uncovered slots have no keys "
6200 "across the cluster:\n");
6201 clusterManagerPrintSlotsList(none);
6202 if (confirmWithYes("Fix these slots by covering with a random node?",
6203 ignore_force)) {
6204 listIter li;
6205 listNode *ln;
6206 listRewind(none, &li);
6207 while ((ln = listNext(&li)) != NULL) {
6208 sds slot = ln->value;
6209 int s = atoi(slot);
6210 clusterManagerNode *n = clusterManagerNodeMasterRandom();
6211 clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
6212 slot, n->ip, n->port);
6213 if (!clusterManagerSetSlotOwner(n, s, 0)) {
6214 fixed = -1;
6215 goto cleanup;
6216 }
6217 /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
6218 * info into the node struct, in order to keep it synced */
6219 n->slots[s] = 1;
6220 fixed++;
6221 }
6222 }
6223 }
6224
6225 /* Handle case "2": keys only in one node. */
6226 if (listLength(single) > 0) {
6227 printf("The following uncovered slots have keys in just one node:\n");
6228 clusterManagerPrintSlotsList(single);
6229 if (confirmWithYes("Fix these slots by covering with those nodes?",
6230 ignore_force)) {
6231 listIter li;
6232 listNode *ln;
6233 listRewind(single, &li);
6234 while ((ln = listNext(&li)) != NULL) {
6235 sds slot = ln->value;
6236 int s = atoi(slot);
6237 dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
6238 assert(entry != NULL);
6239 list *nodes = (list *) dictGetVal(entry);
6240 listNode *fn = listFirst(nodes);
6241 assert(fn != NULL);
6242 clusterManagerNode *n = fn->value;
6243 clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
6244 slot, n->ip, n->port);
6245 if (!clusterManagerSetSlotOwner(n, s, 0)) {
6246 fixed = -1;
6247 goto cleanup;
6248 }
6249 /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
6250 * info into the node struct, in order to keep it synced */
6251 n->slots[atoi(slot)] = 1;
6252 fixed++;
6253 }
6254 }
6255 }
6256
6257 /* Handle case "3": keys in multiple nodes. */
6258 if (listLength(multi) > 0) {
6259 printf("The following uncovered slots have keys in multiple nodes:\n");
6260 clusterManagerPrintSlotsList(multi);
6261 if (confirmWithYes("Fix these slots by moving keys "
6262 "into a single node?", ignore_force)) {
6263 listIter li;
6264 listNode *ln;
6265 listRewind(multi, &li);
6266 while ((ln = listNext(&li)) != NULL) {
6267 sds slot = ln->value;
6268 dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
6269 assert(entry != NULL);
6270 list *nodes = (list *) dictGetVal(entry);
6271 int s = atoi(slot);
6272 clusterManagerNode *target =
6273 clusterManagerGetNodeWithMostKeysInSlot(nodes, s, NULL);
6274 if (target == NULL) {
6275 fixed = -1;
6276 goto cleanup;
6277 }
6278 clusterManagerLogInfo(">>> Covering slot %s moving keys "
6279 "to %s:%d\n", slot,
6280 target->ip, target->port);
6281 if (!clusterManagerSetSlotOwner(target, s, 1)) {
6282 fixed = -1;
6283 goto cleanup;
6284 }
6285 /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
6286 * info into the node struct, in order to keep it synced */
6287 target->slots[atoi(slot)] = 1;
6288 listIter nli;
6289 listNode *nln;
6290 listRewind(nodes, &nli);
6291 while ((nln = listNext(&nli)) != NULL) {
6292 clusterManagerNode *src = nln->value;
6293 if (src == target) continue;
6294 /* Assign the slot to target node in the source node. */
6295 if (!clusterManagerSetSlot(src, target, s, "NODE", NULL))
6296 fixed = -1;
6297 if (fixed < 0) goto cleanup;
6298 /* Set the source node in 'importing' state
6299 * (even if we will actually migrate keys away)
6300 * in order to avoid receiving redirections
6301 * for MIGRATE. */
6302 if (!clusterManagerSetSlot(src, target, s,
6303 "IMPORTING", NULL)) fixed = -1;
6304 if (fixed < 0) goto cleanup;
6305 int opts = CLUSTER_MANAGER_OPT_VERBOSE |
6306 CLUSTER_MANAGER_OPT_COLD;
6307 if (!clusterManagerMoveSlot(src, target, s, opts, NULL)) {
6308 fixed = -1;
6309 goto cleanup;
6310 }
6311 if (!clusterManagerClearSlotStatus(src, s))
6312 fixed = -1;
6313 if (fixed < 0) goto cleanup;
6314 }
6315 fixed++;
6316 }
6317 }
6318 }
6319cleanup:
6320 if (none) listRelease(none);
6321 if (single) listRelease(single);
6322 if (multi) listRelease(multi);
6323 return fixed;
6324}
6325
6326/* Slot 'slot' was found to be in importing or migrating state in one or
6327 * more nodes. This function fixes this condition by migrating keys where
6328 * it seems more sensible. */
6329static int clusterManagerFixOpenSlot(int slot) {
6330 int force_fix = config.cluster_manager_command.flags &
6331 CLUSTER_MANAGER_CMD_FLAG_FIX_WITH_UNREACHABLE_MASTERS;
6332
6333 if (cluster_manager.unreachable_masters > 0 && !force_fix) {
6334 clusterManagerLogWarn("*** Fixing open slots with %d unreachable masters is dangerous: redis-cli will assume that slots about masters that are not reachable are not covered, and will try to reassign them to the reachable nodes. This can cause data loss and is rarely what you want to do. If you really want to proceed use the --cluster-fix-with-unreachable-masters option.\n", cluster_manager.unreachable_masters);
6335 exit(1);
6336 }
6337
6338 clusterManagerLogInfo(">>> Fixing open slot %d\n", slot);
6339 /* Try to obtain the current slot owner, according to the current
6340 * nodes configuration. */
6341 int success = 1;
6342 list *owners = listCreate(); /* List of nodes claiming some ownership.
6343 it could be stating in the configuration
6344 to have the node ownership, or just
6345 holding keys for such slot. */
6346 list *migrating = listCreate();
6347 list *importing = listCreate();
6348 sds migrating_str = sdsempty();
6349 sds importing_str = sdsempty();
6350 clusterManagerNode *owner = NULL; /* The obvious slot owner if any. */
6351
6352 /* Iterate all the nodes, looking for potential owners of this slot. */
6353 listIter li;
6354 listNode *ln;
6355 listRewind(cluster_manager.nodes, &li);
6356 while ((ln = listNext(&li)) != NULL) {
6357 clusterManagerNode *n = ln->value;
6358 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
6359 if (n->slots[slot]) {
6360 listAddNodeTail(owners, n);
6361 } else {
6362 redisReply *r = CLUSTER_MANAGER_COMMAND(n,
6363 "CLUSTER COUNTKEYSINSLOT %d", slot);
6364 success = clusterManagerCheckRedisReply(n, r, NULL);
6365 if (success && r->integer > 0) {
6366 clusterManagerLogWarn("*** Found keys about slot %d "
6367 "in non-owner node %s:%d!\n", slot,
6368 n->ip, n->port);
6369 listAddNodeTail(owners, n);
6370 }
6371 if (r) freeReplyObject(r);
6372 if (!success) goto cleanup;
6373 }
6374 }
6375
6376 /* If we have only a single potential owner for this slot,
6377 * set it as "owner". */
6378 if (listLength(owners) == 1) owner = listFirst(owners)->value;
6379
6380 /* Scan the list of nodes again, in order to populate the
6381 * list of nodes in importing or migrating state for
6382 * this slot. */
6383 listRewind(cluster_manager.nodes, &li);
6384 while ((ln = listNext(&li)) != NULL) {
6385 clusterManagerNode *n = ln->value;
6386 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
6387 int is_migrating = 0, is_importing = 0;
6388 if (n->migrating) {
6389 for (int i = 0; i < n->migrating_count; i += 2) {
6390 sds migrating_slot = n->migrating[i];
6391 if (atoi(migrating_slot) == slot) {
6392 char *sep = (listLength(migrating) == 0 ? "" : ",");
6393 migrating_str = sdscatfmt(migrating_str, "%s%s:%u",
6394 sep, n->ip, n->port);
6395 listAddNodeTail(migrating, n);
6396 is_migrating = 1;
6397 break;
6398 }
6399 }
6400 }
6401 if (!is_migrating && n->importing) {
6402 for (int i = 0; i < n->importing_count; i += 2) {
6403 sds importing_slot = n->importing[i];
6404 if (atoi(importing_slot) == slot) {
6405 char *sep = (listLength(importing) == 0 ? "" : ",");
6406 importing_str = sdscatfmt(importing_str, "%s%s:%u",
6407 sep, n->ip, n->port);
6408 listAddNodeTail(importing, n);
6409 is_importing = 1;
6410 break;
6411 }
6412 }
6413 }
6414
6415 /* If the node is neither migrating nor importing and it's not
6416 * the owner, then is added to the importing list in case
6417 * it has keys in the slot. */
6418 if (!is_migrating && !is_importing && n != owner) {
6419 redisReply *r = CLUSTER_MANAGER_COMMAND(n,
6420 "CLUSTER COUNTKEYSINSLOT %d", slot);
6421 success = clusterManagerCheckRedisReply(n, r, NULL);
6422 if (success && r->integer > 0) {
6423 clusterManagerLogWarn("*** Found keys about slot %d "
6424 "in node %s:%d!\n", slot, n->ip,
6425 n->port);
6426 char *sep = (listLength(importing) == 0 ? "" : ",");
6427 importing_str = sdscatfmt(importing_str, "%s%s:%u",
6428 sep, n->ip, n->port);
6429 listAddNodeTail(importing, n);
6430 }
6431 if (r) freeReplyObject(r);
6432 if (!success) goto cleanup;
6433 }
6434 }
6435 if (sdslen(migrating_str) > 0)
6436 printf("Set as migrating in: %s\n", migrating_str);
6437 if (sdslen(importing_str) > 0)
6438 printf("Set as importing in: %s\n", importing_str);
6439
6440 /* If there is no slot owner, set as owner the node with the biggest
6441 * number of keys, among the set of migrating / importing nodes. */
6442 if (owner == NULL) {
6443 clusterManagerLogInfo(">>> No single clear owner for the slot, "
6444 "selecting an owner by # of keys...\n");
6445 owner = clusterManagerGetNodeWithMostKeysInSlot(cluster_manager.nodes,
6446 slot, NULL);
6447 // If we still don't have an owner, we can't fix it.
6448 if (owner == NULL) {
6449 clusterManagerLogErr("[ERR] Can't select a slot owner. "
6450 "Impossible to fix.\n");
6451 success = 0;
6452 goto cleanup;
6453 }
6454
6455 // Use ADDSLOTS to assign the slot.
6456 clusterManagerLogWarn("*** Configuring %s:%d as the slot owner\n",
6457 owner->ip, owner->port);
6458 success = clusterManagerClearSlotStatus(owner, slot);
6459 if (!success) goto cleanup;
6460 success = clusterManagerSetSlotOwner(owner, slot, 0);
6461 if (!success) goto cleanup;
6462 /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
6463 * info into the node struct, in order to keep it synced */
6464 owner->slots[slot] = 1;
6465 /* Remove the owner from the list of migrating/importing
6466 * nodes. */
6467 clusterManagerRemoveNodeFromList(migrating, owner);
6468 clusterManagerRemoveNodeFromList(importing, owner);
6469 }
6470
6471 /* If there are multiple owners of the slot, we need to fix it
6472 * so that a single node is the owner and all the other nodes
6473 * are in importing state. Later the fix can be handled by one
6474 * of the base cases above.
6475 *
6476 * Note that this case also covers multiple nodes having the slot
6477 * in migrating state, since migrating is a valid state only for
6478 * slot owners. */
6479 if (listLength(owners) > 1) {
6480 /* Owner cannot be NULL at this point, since if there are more owners,
6481 * the owner has been set in the previous condition (owner == NULL). */
6482 assert(owner != NULL);
6483 listRewind(owners, &li);
6484 while ((ln = listNext(&li)) != NULL) {
6485 clusterManagerNode *n = ln->value;
6486 if (n == owner) continue;
6487 success = clusterManagerDelSlot(n, slot, 1);
6488 if (!success) goto cleanup;
6489 n->slots[slot] = 0;
6490 /* Assign the slot to the owner in the node 'n' configuration.' */
6491 success = clusterManagerSetSlot(n, owner, slot, "node", NULL);
6492 if (!success) goto cleanup;
6493 success = clusterManagerSetSlot(n, owner, slot, "importing", NULL);
6494 if (!success) goto cleanup;
6495 /* Avoid duplicates. */
6496 clusterManagerRemoveNodeFromList(importing, n);
6497 listAddNodeTail(importing, n);
6498 /* Ensure that the node is not in the migrating list. */
6499 clusterManagerRemoveNodeFromList(migrating, n);
6500 }
6501 }
6502 int move_opts = CLUSTER_MANAGER_OPT_VERBOSE;
6503
6504 /* Case 1: The slot is in migrating state in one node, and in
6505 * importing state in 1 node. That's trivial to address. */
6506 if (listLength(migrating) == 1 && listLength(importing) == 1) {
6507 clusterManagerNode *src = listFirst(migrating)->value;
6508 clusterManagerNode *dst = listFirst(importing)->value;
6509 clusterManagerLogInfo(">>> Case 1: Moving slot %d from "
6510 "%s:%d to %s:%d\n", slot,
6511 src->ip, src->port, dst->ip, dst->port);
6512 move_opts |= CLUSTER_MANAGER_OPT_UPDATE;
6513 success = clusterManagerMoveSlot(src, dst, slot, move_opts, NULL);
6514 }
6515
6516 /* Case 2: There are multiple nodes that claim the slot as importing,
6517 * they probably got keys about the slot after a restart so opened
6518 * the slot. In this case we just move all the keys to the owner
6519 * according to the configuration. */
6520 else if (listLength(migrating) == 0 && listLength(importing) > 0) {
6521 clusterManagerLogInfo(">>> Case 2: Moving all the %d slot keys to its "
6522 "owner %s:%d\n", slot, owner->ip, owner->port);
6523 move_opts |= CLUSTER_MANAGER_OPT_COLD;
6524 listRewind(importing, &li);
6525 while ((ln = listNext(&li)) != NULL) {
6526 clusterManagerNode *n = ln->value;
6527 if (n == owner) continue;
6528 success = clusterManagerMoveSlot(n, owner, slot, move_opts, NULL);
6529 if (!success) goto cleanup;
6530 clusterManagerLogInfo(">>> Setting %d as STABLE in "
6531 "%s:%d\n", slot, n->ip, n->port);
6532 success = clusterManagerClearSlotStatus(n, slot);
6533 if (!success) goto cleanup;
6534 }
6535 /* Since the slot has been moved in "cold" mode, ensure that all the
6536 * other nodes update their own configuration about the slot itself. */
6537 listRewind(cluster_manager.nodes, &li);
6538 while ((ln = listNext(&li)) != NULL) {
6539 clusterManagerNode *n = ln->value;
6540 if (n == owner) continue;
6541 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
6542 success = clusterManagerSetSlot(n, owner, slot, "NODE", NULL);
6543 if (!success) goto cleanup;
6544 }
6545 }
6546
6547 /* Case 3: The slot is in migrating state in one node but multiple
6548 * other nodes claim to be in importing state and don't have any key in
6549 * the slot. We search for the importing node having the same ID as
6550 * the destination node of the migrating node.
6551 * In that case we move the slot from the migrating node to this node and
6552 * we close the importing states on all the other importing nodes.
6553 * If no importing node has the same ID as the destination node of the
6554 * migrating node, the slot's state is closed on both the migrating node
6555 * and the importing nodes. */
6556 else if (listLength(migrating) == 1 && listLength(importing) > 1) {
6557 int try_to_fix = 1;
6558 clusterManagerNode *src = listFirst(migrating)->value;
6559 clusterManagerNode *dst = NULL;
6560 sds target_id = NULL;
6561 for (int i = 0; i < src->migrating_count; i += 2) {
6562 sds migrating_slot = src->migrating[i];
6563 if (atoi(migrating_slot) == slot) {
6564 target_id = src->migrating[i + 1];
6565 break;
6566 }
6567 }
6568 assert(target_id != NULL);
6569 listIter li;
6570 listNode *ln;
6571 listRewind(importing, &li);
6572 while ((ln = listNext(&li)) != NULL) {
6573 clusterManagerNode *n = ln->value;
6574 int count = clusterManagerCountKeysInSlot(n, slot);
6575 if (count > 0) {
6576 try_to_fix = 0;
6577 break;
6578 }
6579 if (strcmp(n->name, target_id) == 0) dst = n;
6580 }
6581 if (!try_to_fix) goto unhandled_case;
6582 if (dst != NULL) {
6583 clusterManagerLogInfo(">>> Case 3: Moving slot %d from %s:%d to "
6584 "%s:%d and closing it on all the other "
6585 "importing nodes.\n",
6586 slot, src->ip, src->port,
6587 dst->ip, dst->port);
6588 /* Move the slot to the destination node. */
6589 success = clusterManagerMoveSlot(src, dst, slot, move_opts, NULL);
6590 if (!success) goto cleanup;
6591 /* Close slot on all the other importing nodes. */
6592 listRewind(importing, &li);
6593 while ((ln = listNext(&li)) != NULL) {
6594 clusterManagerNode *n = ln->value;
6595 if (dst == n) continue;
6596 success = clusterManagerClearSlotStatus(n, slot);
6597 if (!success) goto cleanup;
6598 }
6599 } else {
6600 clusterManagerLogInfo(">>> Case 3: Closing slot %d on both "
6601 "migrating and importing nodes.\n", slot);
6602 /* Close the slot on both the migrating node and the importing
6603 * nodes. */
6604 success = clusterManagerClearSlotStatus(src, slot);
6605 if (!success) goto cleanup;
6606 listRewind(importing, &li);
6607 while ((ln = listNext(&li)) != NULL) {
6608 clusterManagerNode *n = ln->value;
6609 success = clusterManagerClearSlotStatus(n, slot);
6610 if (!success) goto cleanup;
6611 }
6612 }
6613 } else {
6614 int try_to_close_slot = (listLength(importing) == 0 &&
6615 listLength(migrating) == 1);
6616 if (try_to_close_slot) {
6617 clusterManagerNode *n = listFirst(migrating)->value;
6618 if (!owner || owner != n) {
6619 redisReply *r = CLUSTER_MANAGER_COMMAND(n,
6620 "CLUSTER GETKEYSINSLOT %d %d", slot, 10);
6621 success = clusterManagerCheckRedisReply(n, r, NULL);
6622 if (r) {
6623 if (success) try_to_close_slot = (r->elements == 0);
6624 freeReplyObject(r);
6625 }
6626 if (!success) goto cleanup;
6627 }
6628 }
6629 /* Case 4: There are no slots claiming to be in importing state, but
6630 * there is a migrating node that actually don't have any key or is the
6631 * slot owner. We can just close the slot, probably a reshard
6632 * interrupted in the middle. */
6633 if (try_to_close_slot) {
6634 clusterManagerNode *n = listFirst(migrating)->value;
6635 clusterManagerLogInfo(">>> Case 4: Closing slot %d on %s:%d\n",
6636 slot, n->ip, n->port);
6637 redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER SETSLOT %d %s",
6638 slot, "STABLE");
6639 success = clusterManagerCheckRedisReply(n, r, NULL);
6640 if (r) freeReplyObject(r);
6641 if (!success) goto cleanup;
6642 } else {
6643unhandled_case:
6644 success = 0;
6645 clusterManagerLogErr("[ERR] Sorry, redis-cli can't fix this slot "
6646 "yet (work in progress). Slot is set as "
6647 "migrating in %s, as importing in %s, "
6648 "owner is %s:%d\n", migrating_str,
6649 importing_str, owner->ip, owner->port);
6650 }
6651 }
6652cleanup:
6653 listRelease(owners);
6654 listRelease(migrating);
6655 listRelease(importing);
6656 sdsfree(migrating_str);
6657 sdsfree(importing_str);
6658 return success;
6659}
6660
6661static int clusterManagerFixMultipleSlotOwners(int slot, list *owners) {
6662 clusterManagerLogInfo(">>> Fixing multiple owners for slot %d...\n", slot);
6663 int success = 0;
6664 assert(listLength(owners) > 1);
6665 clusterManagerNode *owner = clusterManagerGetNodeWithMostKeysInSlot(owners,
6666 slot,
6667 NULL);
6668 if (!owner) owner = listFirst(owners)->value;
6669 clusterManagerLogInfo(">>> Setting slot %d owner: %s:%d\n",
6670 slot, owner->ip, owner->port);
6671 /* Set the slot owner. */
6672 if (!clusterManagerSetSlotOwner(owner, slot, 0)) return 0;
6673 listIter li;
6674 listNode *ln;
6675 listRewind(cluster_manager.nodes, &li);
6676 /* Update configuration in all the other master nodes by assigning the slot
6677 * itself to the new owner, and by eventually migrating keys if the node
6678 * has keys for the slot. */
6679 while ((ln = listNext(&li)) != NULL) {
6680 clusterManagerNode *n = ln->value;
6681 if (n == owner) continue;
6682 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
6683 int count = clusterManagerCountKeysInSlot(n, slot);
6684 success = (count >= 0);
6685 if (!success) break;
6686 clusterManagerDelSlot(n, slot, 1);
6687 if (!clusterManagerSetSlot(n, owner, slot, "node", NULL)) return 0;
6688 if (count > 0) {
6689 int opts = CLUSTER_MANAGER_OPT_VERBOSE |
6690 CLUSTER_MANAGER_OPT_COLD;
6691 success = clusterManagerMoveSlot(n, owner, slot, opts, NULL);
6692 if (!success) break;
6693 }
6694 }
6695 return success;
6696}
6697
6698static int clusterManagerCheckCluster(int quiet) {
6699 listNode *ln = listFirst(cluster_manager.nodes);
6700 if (!ln) return 0;
6701 clusterManagerNode *node = ln->value;
6702 clusterManagerLogInfo(">>> Performing Cluster Check (using node %s:%d)\n",
6703 node->ip, node->port);
6704 int result = 1, consistent = 0;
6705 int do_fix = config.cluster_manager_command.flags &
6706 CLUSTER_MANAGER_CMD_FLAG_FIX;
6707 if (!quiet) clusterManagerShowNodes();
6708 consistent = clusterManagerIsConfigConsistent();
6709 if (!consistent) {
6710 sds err = sdsnew("[ERR] Nodes don't agree about configuration!");
6711 clusterManagerOnError(err);
6712 result = 0;
6713 } else {
6714 clusterManagerLogOk("[OK] All nodes agree about slots "
6715 "configuration.\n");
6716 }
6717 /* Check open slots */
6718 clusterManagerLogInfo(">>> Check for open slots...\n");
6719 listIter li;
6720 listRewind(cluster_manager.nodes, &li);
6721 int i;
6722 dict *open_slots = NULL;
6723 while ((ln = listNext(&li)) != NULL) {
6724 clusterManagerNode *n = ln->value;
6725 if (n->migrating != NULL) {
6726 if (open_slots == NULL)
6727 open_slots = dictCreate(&clusterManagerDictType);
6728 sds errstr = sdsempty();
6729 errstr = sdscatprintf(errstr,
6730 "[WARNING] Node %s:%d has slots in "
6731 "migrating state ",
6732 n->ip,
6733 n->port);
6734 for (i = 0; i < n->migrating_count; i += 2) {
6735 sds slot = n->migrating[i];
6736 dictReplace(open_slots, slot, sdsdup(n->migrating[i + 1]));
6737 char *fmt = (i > 0 ? ",%S" : "%S");
6738 errstr = sdscatfmt(errstr, fmt, slot);
6739 }
6740 errstr = sdscat(errstr, ".");
6741 clusterManagerOnError(errstr);
6742 }
6743 if (n->importing != NULL) {
6744 if (open_slots == NULL)
6745 open_slots = dictCreate(&clusterManagerDictType);
6746 sds errstr = sdsempty();
6747 errstr = sdscatprintf(errstr,
6748 "[WARNING] Node %s:%d has slots in "
6749 "importing state ",
6750 n->ip,
6751 n->port);
6752 for (i = 0; i < n->importing_count; i += 2) {
6753 sds slot = n->importing[i];
6754 dictReplace(open_slots, slot, sdsdup(n->importing[i + 1]));
6755 char *fmt = (i > 0 ? ",%S" : "%S");
6756 errstr = sdscatfmt(errstr, fmt, slot);
6757 }
6758 errstr = sdscat(errstr, ".");
6759 clusterManagerOnError(errstr);
6760 }
6761 }
6762 if (open_slots != NULL) {
6763 result = 0;
6764 dictIterator iter;
6765 dictEntry *entry;
6766 sds errstr = sdsnew("[WARNING] The following slots are open: ");
6767 i = 0;
6768
6769 dictInitIterator(&iter, open_slots);
6770 while ((entry = dictNext(&iter)) != NULL) {
6771 sds slot = (sds) dictGetKey(entry);
6772 char *fmt = (i++ > 0 ? ",%S" : "%S");
6773 errstr = sdscatfmt(errstr, fmt, slot);
6774 }
6775 dictResetIterator(&iter);
6776 clusterManagerLogErr("%s.\n", (char *) errstr);
6777 sdsfree(errstr);
6778 if (do_fix) {
6779 /* Fix open slots. */
6780 dictInitIterator(&iter, open_slots);
6781 while ((entry = dictNext(&iter)) != NULL) {
6782 sds slot = (sds) dictGetKey(entry);
6783 result = clusterManagerFixOpenSlot(atoi(slot));
6784 if (!result) break;
6785 }
6786 dictResetIterator(&iter);
6787 }
6788 dictRelease(open_slots);
6789 }
6790 clusterManagerLogInfo(">>> Check slots coverage...\n");
6791 char slots[CLUSTER_MANAGER_SLOTS];
6792 memset(slots, 0, CLUSTER_MANAGER_SLOTS);
6793 int coverage = clusterManagerGetCoveredSlots(slots);
6794 if (coverage == CLUSTER_MANAGER_SLOTS) {
6795 clusterManagerLogOk("[OK] All %d slots covered.\n",
6796 CLUSTER_MANAGER_SLOTS);
6797 } else {
6798 sds err = sdsempty();
6799 err = sdscatprintf(err, "[ERR] Not all %d slots are "
6800 "covered by nodes.\n",
6801 CLUSTER_MANAGER_SLOTS);
6802 clusterManagerOnError(err);
6803 result = 0;
6804 if (do_fix/* && result*/) {
6805 dictType dtype = clusterManagerDictType;
6806 dtype.keyDestructor = dictSdsDestructor;
6807 dtype.valDestructor = dictListDestructor;
6808 clusterManagerUncoveredSlots = dictCreate(&dtype);
6809 int fixed = clusterManagerFixSlotsCoverage(slots);
6810 if (fixed > 0) result = 1;
6811 }
6812 }
6813 int search_multiple_owners = config.cluster_manager_command.flags &
6814 CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
6815 if (search_multiple_owners) {
6816 /* Check whether there are multiple owners, even when slots are
6817 * fully covered and there are no open slots. */
6818 clusterManagerLogInfo(">>> Check for multiple slot owners...\n");
6819 int slot = 0, slots_with_multiple_owners = 0;
6820 for (; slot < CLUSTER_MANAGER_SLOTS; slot++) {
6821 listIter li;
6822 listNode *ln;
6823 listRewind(cluster_manager.nodes, &li);
6824 list *owners = listCreate();
6825 while ((ln = listNext(&li)) != NULL) {
6826 clusterManagerNode *n = ln->value;
6827 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
6828 if (n->slots[slot]) listAddNodeTail(owners, n);
6829 else {
6830 /* Nodes having keys for the slot will be considered
6831 * owners too. */
6832 int count = clusterManagerCountKeysInSlot(n, slot);
6833 if (count > 0) listAddNodeTail(owners, n);
6834 }
6835 }
6836 if (listLength(owners) > 1) {
6837 result = 0;
6838 clusterManagerLogErr("[WARNING] Slot %d has %d owners:\n",
6839 slot, listLength(owners));
6840 listRewind(owners, &li);
6841 while ((ln = listNext(&li)) != NULL) {
6842 clusterManagerNode *n = ln->value;
6843 clusterManagerLogErr(" %s:%d\n", n->ip, n->port);
6844 }
6845 slots_with_multiple_owners++;
6846 if (do_fix) {
6847 result = clusterManagerFixMultipleSlotOwners(slot, owners);
6848 if (!result) {
6849 clusterManagerLogErr("Failed to fix multiple owners "
6850 "for slot %d\n", slot);
6851 listRelease(owners);
6852 break;
6853 } else slots_with_multiple_owners--;
6854 }
6855 }
6856 listRelease(owners);
6857 }
6858 if (slots_with_multiple_owners == 0)
6859 clusterManagerLogOk("[OK] No multiple owners found.\n");
6860 }
6861 return result;
6862}
6863
6864static clusterManagerNode *clusterNodeForResharding(char *id,
6865 clusterManagerNode *target,
6866 int *raise_err)
6867{
6868 clusterManagerNode *node = NULL;
6869 const char *invalid_node_msg = "*** The specified node (%s) is not known "
6870 "or not a master, please retry.\n";
6871 node = clusterManagerNodeByName(id);
6872 *raise_err = 0;
6873 if (!node || node->flags & CLUSTER_MANAGER_FLAG_SLAVE) {
6874 clusterManagerLogErr(invalid_node_msg, id);
6875 *raise_err = 1;
6876 return NULL;
6877 } else if (target != NULL) {
6878 if (!strcmp(node->name, target->name)) {
6879 clusterManagerLogErr( "*** It is not possible to use "
6880 "the target node as "
6881 "source node.\n");
6882 return NULL;
6883 }
6884 }
6885 return node;
6886}
6887
6888static list *clusterManagerComputeReshardTable(list *sources, int numslots) {
6889 list *moved = listCreate();
6890 int src_count = listLength(sources), i = 0, tot_slots = 0, j;
6891 clusterManagerNode **sorted = zmalloc(src_count * sizeof(*sorted));
6892 listIter li;
6893 listNode *ln;
6894 listRewind(sources, &li);
6895 while ((ln = listNext(&li)) != NULL) {
6896 clusterManagerNode *node = ln->value;
6897 tot_slots += node->slots_count;
6898 sorted[i++] = node;
6899 }
6900 qsort(sorted, src_count, sizeof(clusterManagerNode *),
6901 clusterManagerSlotCountCompareDesc);
6902 for (i = 0; i < src_count; i++) {
6903 clusterManagerNode *node = sorted[i];
6904 float n = ((float) numslots / tot_slots * node->slots_count);
6905 if (i == 0) n = ceil(n);
6906 else n = floor(n);
6907 int max = (int) n, count = 0;
6908 for (j = 0; j < CLUSTER_MANAGER_SLOTS; j++) {
6909 int slot = node->slots[j];
6910 if (!slot) continue;
6911 if (count >= max || (int)listLength(moved) >= numslots) break;
6912 clusterManagerReshardTableItem *item = zmalloc(sizeof(*item));
6913 item->source = node;
6914 item->slot = j;
6915 listAddNodeTail(moved, item);
6916 count++;
6917 }
6918 }
6919 zfree(sorted);
6920 return moved;
6921}
6922
6923static void clusterManagerShowReshardTable(list *table) {
6924 listIter li;
6925 listNode *ln;
6926 listRewind(table, &li);
6927 while ((ln = listNext(&li)) != NULL) {
6928 clusterManagerReshardTableItem *item = ln->value;
6929 clusterManagerNode *n = item->source;
6930 printf(" Moving slot %d from %s\n", item->slot, (char *) n->name);
6931 }
6932}
6933
6934static void clusterManagerReleaseReshardTable(list *table) {
6935 if (table != NULL) {
6936 listIter li;
6937 listNode *ln;
6938 listRewind(table, &li);
6939 while ((ln = listNext(&li)) != NULL) {
6940 clusterManagerReshardTableItem *item = ln->value;
6941 zfree(item);
6942 }
6943 listRelease(table);
6944 }
6945}
6946
6947static void clusterManagerLog(int level, const char* fmt, ...) {
6948 int use_colors =
6949 (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_COLOR);
6950 if (use_colors) {
6951 printf("\033[");
6952 switch (level) {
6953 case CLUSTER_MANAGER_LOG_LVL_INFO: printf(LOG_COLOR_BOLD); break;
6954 case CLUSTER_MANAGER_LOG_LVL_WARN: printf(LOG_COLOR_YELLOW); break;
6955 case CLUSTER_MANAGER_LOG_LVL_ERR: printf(LOG_COLOR_RED); break;
6956 case CLUSTER_MANAGER_LOG_LVL_SUCCESS: printf(LOG_COLOR_GREEN); break;
6957 default: printf(LOG_COLOR_RESET); break;
6958 }
6959 }
6960 va_list ap;
6961 va_start(ap, fmt);
6962 vprintf(fmt, ap);
6963 va_end(ap);
6964 if (use_colors) printf("\033[" LOG_COLOR_RESET);
6965}
6966
6967static void clusterManagerNodeArrayInit(clusterManagerNodeArray *array,
6968 int alloc_len)
6969{
6970 array->nodes = zcalloc(alloc_len * sizeof(clusterManagerNode*));
6971 array->alloc = array->nodes;
6972 array->len = alloc_len;
6973 array->count = 0;
6974}
6975
6976/* Reset array->nodes to the original array allocation and re-count non-NULL
6977 * nodes. */
6978static void clusterManagerNodeArrayReset(clusterManagerNodeArray *array) {
6979 if (array->nodes > array->alloc) {
6980 array->len = array->nodes - array->alloc;
6981 array->nodes = array->alloc;
6982 array->count = 0;
6983 int i = 0;
6984 for(; i < array->len; i++) {
6985 if (array->nodes[i] != NULL) array->count++;
6986 }
6987 }
6988}
6989
6990/* Shift array->nodes and store the shifted node into 'nodeptr'. */
6991static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array,
6992 clusterManagerNode **nodeptr)
6993{
6994 assert(array->len > 0);
6995 /* If the first node to be shifted is not NULL, decrement count. */
6996 if (*array->nodes != NULL) array->count--;
6997 /* Store the first node to be shifted into 'nodeptr'. */
6998 *nodeptr = *array->nodes;
6999 /* Shift the nodes array and decrement length. */
7000 array->nodes++;
7001 array->len--;
7002}
7003
7004static void clusterManagerNodeArrayAdd(clusterManagerNodeArray *array,
7005 clusterManagerNode *node)
7006{
7007 assert(array->len > 0);
7008 assert(node != NULL);
7009 assert(array->count < array->len);
7010 array->nodes[array->count++] = node;
7011}
7012
7013static void clusterManagerPrintNotEmptyNodeError(clusterManagerNode *node,
7014 char *err)
7015{
7016 char *msg;
7017 if (err) msg = err;
7018 else {
7019 msg = "is not empty. Either the node already knows other "
7020 "nodes (check with CLUSTER NODES) or contains some "
7021 "key in database 0.";
7022 }
7023 clusterManagerLogErr("[ERR] Node %s:%d %s\n", node->ip, node->port, msg);
7024}
7025
7026static void clusterManagerPrintNotClusterNodeError(clusterManagerNode *node,
7027 char *err)
7028{
7029 char *msg = (err ? err : "is not configured as a cluster node.");
7030 clusterManagerLogErr("[ERR] Node %s:%d %s\n", node->ip, node->port, msg);
7031}
7032
7033/* Execute redis-cli in Cluster Manager mode */
7034static void clusterManagerMode(clusterManagerCommandProc *proc) {
7035 int argc = config.cluster_manager_command.argc;
7036 char **argv = config.cluster_manager_command.argv;
7037 cluster_manager.nodes = NULL;
7038 int success = proc(argc, argv);
7039
7040 /* Initialized in createClusterManagerCommand. */
7041 if (config.stdin_lastarg) {
7042 zfree(config.cluster_manager_command.argv);
7043 sdsfree(config.cluster_manager_command.stdin_arg);
7044 } else if (config.stdin_tag_arg) {
7045 sdsfree(config.cluster_manager_command.stdin_arg);
7046 }
7047 freeClusterManager();
7048
7049 exit(success ? 0 : 1);
7050}
7051
7052/* Cluster Manager Commands */
7053
7054static int clusterManagerCommandCreate(int argc, char **argv) {
7055 int i, j, success = 1;
7056 cluster_manager.nodes = listCreate();
7057 for (i = 0; i < argc; i++) {
7058 char *addr = argv[i];
7059 char *ip = NULL;
7060 int port = 0;
7061 if (!parseClusterNodeAddress(addr, &ip, &port, NULL)) {
7062 fprintf(stderr, "Invalid address format: %s\n", addr);
7063 return 0;
7064 }
7065
7066 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
7067 if (!clusterManagerNodeConnect(node)) {
7068 freeClusterManagerNode(node);
7069 return 0;
7070 }
7071 char *err = NULL;
7072 if (!clusterManagerNodeIsCluster(node, &err)) {
7073 clusterManagerPrintNotClusterNodeError(node, err);
7074 if (err) zfree(err);
7075 freeClusterManagerNode(node);
7076 return 0;
7077 }
7078 err = NULL;
7079 if (!clusterManagerNodeLoadInfo(node, 0, &err)) {
7080 if (err) {
7081 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
7082 zfree(err);
7083 }
7084 freeClusterManagerNode(node);
7085 return 0;
7086 }
7087 err = NULL;
7088 if (!clusterManagerNodeIsEmpty(node, &err)) {
7089 clusterManagerPrintNotEmptyNodeError(node, err);
7090 if (err) zfree(err);
7091 freeClusterManagerNode(node);
7092 return 0;
7093 }
7094 listAddNodeTail(cluster_manager.nodes, node);
7095 }
7096 int node_len = cluster_manager.nodes->len;
7097 int replicas = config.cluster_manager_command.replicas;
7098 int masters_count = CLUSTER_MANAGER_MASTERS_COUNT(node_len, replicas);
7099 if (masters_count < 3) {
7100 clusterManagerLogErr(
7101 "*** ERROR: Invalid configuration for cluster creation.\n"
7102 "*** Redis Cluster requires at least 3 master nodes.\n"
7103 "*** This is not possible with %d nodes and %d replicas per node.",
7104 node_len, replicas);
7105 clusterManagerLogErr("\n*** At least %d nodes are required.\n",
7106 3 * (replicas + 1));
7107 return 0;
7108 }
7109 clusterManagerLogInfo(">>> Performing hash slots allocation "
7110 "on %d nodes...\n", node_len);
7111 int interleaved_len = 0, ip_count = 0;
7112 clusterManagerNode **interleaved = zcalloc(node_len*sizeof(**interleaved));
7113 char **ips = zcalloc(node_len * sizeof(char*));
7114 clusterManagerNodeArray *ip_nodes = zcalloc(node_len * sizeof(*ip_nodes));
7115 listIter li;
7116 listNode *ln;
7117 listRewind(cluster_manager.nodes, &li);
7118 while ((ln = listNext(&li)) != NULL) {
7119 clusterManagerNode *n = ln->value;
7120 int found = 0;
7121 for (i = 0; i < ip_count; i++) {
7122 char *ip = ips[i];
7123 if (!strcmp(ip, n->ip)) {
7124 found = 1;
7125 break;
7126 }
7127 }
7128 if (!found) {
7129 ips[ip_count++] = n->ip;
7130 }
7131 clusterManagerNodeArray *node_array = &(ip_nodes[i]);
7132 if (node_array->nodes == NULL)
7133 clusterManagerNodeArrayInit(node_array, node_len);
7134 clusterManagerNodeArrayAdd(node_array, n);
7135 }
7136 while (interleaved_len < node_len) {
7137 for (i = 0; i < ip_count; i++) {
7138 clusterManagerNodeArray *node_array = &(ip_nodes[i]);
7139 if (node_array->count > 0) {
7140 clusterManagerNode *n = NULL;
7141 clusterManagerNodeArrayShift(node_array, &n);
7142 interleaved[interleaved_len++] = n;
7143 }
7144 }
7145 }
7146 clusterManagerNode **masters = interleaved;
7147 interleaved += masters_count;
7148 interleaved_len -= masters_count;
7149 float slots_per_node = CLUSTER_MANAGER_SLOTS / (float) masters_count;
7150 long first = 0;
7151 float cursor = 0.0f;
7152 for (i = 0; i < masters_count; i++) {
7153 clusterManagerNode *master = masters[i];
7154 long last = lround(cursor + slots_per_node - 1);
7155 if (last > CLUSTER_MANAGER_SLOTS || i == (masters_count - 1))
7156 last = CLUSTER_MANAGER_SLOTS - 1;
7157 if (last < first) last = first;
7158 printf("Master[%d] -> Slots %ld - %ld\n", i, first, last);
7159 master->slots_count = 0;
7160 for (j = first; j <= last; j++) {
7161 master->slots[j] = 1;
7162 master->slots_count++;
7163 }
7164 master->dirty = 1;
7165 first = last + 1;
7166 cursor += slots_per_node;
7167 }
7168
7169 /* Rotating the list sometimes helps to get better initial
7170 * anti-affinity before the optimizer runs. */
7171 clusterManagerNode *first_node = interleaved[0];
7172 for (i = 0; i < (interleaved_len - 1); i++)
7173 interleaved[i] = interleaved[i + 1];
7174 interleaved[interleaved_len - 1] = first_node;
7175 int assign_unused = 0, available_count = interleaved_len;
7176assign_replicas:
7177 for (i = 0; i < masters_count; i++) {
7178 clusterManagerNode *master = masters[i];
7179 int assigned_replicas = 0;
7180 while (assigned_replicas < replicas) {
7181 if (available_count == 0) break;
7182 clusterManagerNode *found = NULL, *slave = NULL;
7183 int firstNodeIdx = -1;
7184 for (j = 0; j < interleaved_len; j++) {
7185 clusterManagerNode *n = interleaved[j];
7186 if (n == NULL) continue;
7187 if (strcmp(n->ip, master->ip)) {
7188 found = n;
7189 interleaved[j] = NULL;
7190 break;
7191 }
7192 if (firstNodeIdx < 0) firstNodeIdx = j;
7193 }
7194 if (found) slave = found;
7195 else if (firstNodeIdx >= 0) {
7196 slave = interleaved[firstNodeIdx];
7197 interleaved_len -= (firstNodeIdx + 1);
7198 interleaved += (firstNodeIdx + 1);
7199 }
7200 if (slave != NULL) {
7201 assigned_replicas++;
7202 available_count--;
7203 if (slave->replicate) sdsfree(slave->replicate);
7204 slave->replicate = sdsnew(master->name);
7205 slave->dirty = 1;
7206 } else break;
7207 printf("Adding replica %s:%d to %s:%d\n", slave->ip, slave->port,
7208 master->ip, master->port);
7209 if (assign_unused) break;
7210 }
7211 }
7212 if (!assign_unused && available_count > 0) {
7213 assign_unused = 1;
7214 printf("Adding extra replicas...\n");
7215 goto assign_replicas;
7216 }
7217 for (i = 0; i < ip_count; i++) {
7218 clusterManagerNodeArray *node_array = ip_nodes + i;
7219 clusterManagerNodeArrayReset(node_array);
7220 }
7221 clusterManagerOptimizeAntiAffinity(ip_nodes, ip_count);
7222 clusterManagerShowNodes();
7223 int ignore_force = 0;
7224 if (confirmWithYes("Can I set the above configuration?", ignore_force)) {
7225 listRewind(cluster_manager.nodes, &li);
7226 while ((ln = listNext(&li)) != NULL) {
7227 clusterManagerNode *node = ln->value;
7228 char *err = NULL;
7229 int flushed = clusterManagerFlushNodeConfig(node, &err);
7230 if (!flushed && node->dirty && !node->replicate) {
7231 if (err != NULL) {
7232 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
7233 zfree(err);
7234 }
7235 success = 0;
7236 goto cleanup;
7237 } else if (err != NULL) zfree(err);
7238 }
7239 clusterManagerLogInfo(">>> Nodes configuration updated\n");
7240 clusterManagerLogInfo(">>> Assign a different config epoch to "
7241 "each node\n");
7242 int config_epoch = 1;
7243 listRewind(cluster_manager.nodes, &li);
7244 while ((ln = listNext(&li)) != NULL) {
7245 clusterManagerNode *node = ln->value;
7246 redisReply *reply = NULL;
7247 reply = CLUSTER_MANAGER_COMMAND(node,
7248 "cluster set-config-epoch %d",
7249 config_epoch++);
7250 if (reply != NULL) freeReplyObject(reply);
7251 }
7252 clusterManagerLogInfo(">>> Sending CLUSTER MEET messages to join "
7253 "the cluster\n");
7254 clusterManagerNode *first = NULL;
7255 char first_ip[NET_IP_STR_LEN]; /* first->ip may be a hostname */
7256 listRewind(cluster_manager.nodes, &li);
7257 while ((ln = listNext(&li)) != NULL) {
7258 clusterManagerNode *node = ln->value;
7259 if (first == NULL) {
7260 first = node;
7261 /* Although hiredis supports connecting to a hostname, CLUSTER
7262 * MEET requires an IP address, so we do a DNS lookup here. */
7263 int anet_flags = ANET_NONE;
7264 if (config.prefer_ipv4) anet_flags |= ANET_PREFER_IPV4;
7265 if (config.prefer_ipv6) anet_flags |= ANET_PREFER_IPV6;
7266 if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), anet_flags)
7267 == ANET_ERR)
7268 {
7269 fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip);
7270 success = 0;
7271 goto cleanup;
7272 }
7273 continue;
7274 }
7275 redisReply *reply = NULL;
7276 if (first->bus_port == 0 || (first->bus_port == first->port + CLUSTER_MANAGER_PORT_INCR)) {
7277 /* CLUSTER MEET bus-port parameter was added in 4.0.
7278 * So if (bus_port == 0) or (bus_port == port + CLUSTER_MANAGER_PORT_INCR),
7279 * we just call CLUSTER MEET with 2 arguments, using the old form. */
7280 reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d",
7281 first_ip, first->port);
7282 } else {
7283 reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d %d",
7284 first_ip, first->port, first->bus_port);
7285 }
7286 int is_err = 0;
7287 if (reply != NULL) {
7288 if ((is_err = reply->type == REDIS_REPLY_ERROR))
7289 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, reply->str);
7290 freeReplyObject(reply);
7291 } else {
7292 is_err = 1;
7293 fprintf(stderr, "Failed to send CLUSTER MEET command.\n");
7294 }
7295 if (is_err) {
7296 success = 0;
7297 goto cleanup;
7298 }
7299 }
7300 /* Give one second for the join to start, in order to avoid that
7301 * waiting for cluster join will find all the nodes agree about
7302 * the config as they are still empty with unassigned slots. */
7303 sleep(1);
7304 clusterManagerWaitForClusterJoin();
7305 /* Useful for the replicas */
7306 listRewind(cluster_manager.nodes, &li);
7307 while ((ln = listNext(&li)) != NULL) {
7308 clusterManagerNode *node = ln->value;
7309 if (!node->dirty) continue;
7310 char *err = NULL;
7311 int flushed = clusterManagerFlushNodeConfig(node, &err);
7312 if (!flushed && !node->replicate) {
7313 if (err != NULL) {
7314 CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
7315 zfree(err);
7316 }
7317 success = 0;
7318 goto cleanup;
7319 } else if (err != NULL) {
7320 zfree(err);
7321 }
7322 }
7323 // Reset Nodes
7324 listRewind(cluster_manager.nodes, &li);
7325 clusterManagerNode *first_node = NULL;
7326 while ((ln = listNext(&li)) != NULL) {
7327 clusterManagerNode *node = ln->value;
7328 if (!first_node) first_node = node;
7329 else freeClusterManagerNode(node);
7330 }
7331 listEmpty(cluster_manager.nodes);
7332 if (!clusterManagerLoadInfoFromNode(first_node)) {
7333 success = 0;
7334 goto cleanup;
7335 }
7336 clusterManagerCheckCluster(0);
7337 }
7338cleanup:
7339 /* Free everything */
7340 zfree(masters);
7341 zfree(ips);
7342 for (i = 0; i < node_len; i++) {
7343 clusterManagerNodeArray *node_array = ip_nodes + i;
7344 CLUSTER_MANAGER_NODE_ARRAY_FREE(node_array);
7345 }
7346 zfree(ip_nodes);
7347 return success;
7348}
7349
7350static int clusterManagerCommandAddNode(int argc, char **argv) {
7351 int success = 1;
7352 redisReply *reply = NULL;
7353 redisReply *function_restore_reply = NULL;
7354 redisReply *function_list_reply = NULL;
7355 char *ref_ip = NULL, *ip = NULL;
7356 int ref_port = 0, port = 0;
7357 if (!getClusterHostFromCmdArgs(argc - 1, argv + 1, &ref_ip, &ref_port))
7358 goto invalid_args;
7359 if (!getClusterHostFromCmdArgs(1, argv, &ip, &port))
7360 goto invalid_args;
7361 clusterManagerLogInfo(">>> Adding node %s:%d to cluster %s:%d\n", ip, port,
7362 ref_ip, ref_port);
7363 // Check the existing cluster
7364 clusterManagerNode *refnode = clusterManagerNewNode(ref_ip, ref_port, 0);
7365 if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
7366 if (!clusterManagerCheckCluster(0)) return 0;
7367
7368 /* If --cluster-master-id was specified, try to resolve it now so that we
7369 * abort before starting with the node configuration. */
7370 clusterManagerNode *master_node = NULL;
7371 if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_SLAVE) {
7372 char *master_id = config.cluster_manager_command.master_id;
7373 if (master_id != NULL) {
7374 master_node = clusterManagerNodeByName(master_id);
7375 if (master_node == NULL) {
7376 clusterManagerLogErr("[ERR] No such master ID %s\n", master_id);
7377 return 0;
7378 }
7379 } else {
7380 master_node = clusterManagerNodeWithLeastReplicas();
7381 assert(master_node != NULL);
7382 printf("Automatically selected master %s:%d\n", master_node->ip,
7383 master_node->port);
7384 }
7385 }
7386
7387 // Add the new node
7388 clusterManagerNode *new_node = clusterManagerNewNode(ip, port, 0);
7389 int added = 0;
7390 if (!clusterManagerNodeConnect(new_node)) {
7391 clusterManagerLogErr("[ERR] Sorry, can't connect to node %s:%d\n",
7392 ip, port);
7393 success = 0;
7394 goto cleanup;
7395 }
7396 char *err = NULL;
7397 if (!(success = clusterManagerNodeIsCluster(new_node, &err))) {
7398 clusterManagerPrintNotClusterNodeError(new_node, err);
7399 if (err) zfree(err);
7400 goto cleanup;
7401 }
7402 if (!clusterManagerNodeLoadInfo(new_node, 0, &err)) {
7403 if (err) {
7404 CLUSTER_MANAGER_PRINT_REPLY_ERROR(new_node, err);
7405 zfree(err);
7406 }
7407 success = 0;
7408 goto cleanup;
7409 }
7410 if (!(success = clusterManagerNodeIsEmpty(new_node, &err))) {
7411 clusterManagerPrintNotEmptyNodeError(new_node, err);
7412 if (err) zfree(err);
7413 goto cleanup;
7414 }
7415 clusterManagerNode *first = listFirst(cluster_manager.nodes)->value;
7416 listAddNodeTail(cluster_manager.nodes, new_node);
7417 added = 1;
7418
7419 if (!master_node) {
7420 /* Send functions to the new node, if new node is a replica it will get the functions from its primary. */
7421 clusterManagerLogInfo(">>> Getting functions from cluster\n");
7422 reply = CLUSTER_MANAGER_COMMAND(refnode, "FUNCTION DUMP");
7423 if (!clusterManagerCheckRedisReply(refnode, reply, &err)) {
7424 clusterManagerLogInfo(">>> Failed retrieving Functions from the cluster, "
7425 "skip this step as Redis version do not support function command (error = '%s')\n", err? err : "NULL reply");
7426 if (err) zfree(err);
7427 } else {
7428 assert(reply->type == REDIS_REPLY_STRING);
7429 clusterManagerLogInfo(">>> Send FUNCTION LIST to %s:%d to verify there is no functions in it\n", ip, port);
7430 function_list_reply = CLUSTER_MANAGER_COMMAND(new_node, "FUNCTION LIST");
7431 if (!clusterManagerCheckRedisReply(new_node, function_list_reply, &err)) {
7432 clusterManagerLogErr(">>> Failed on CLUSTER LIST (error = '%s')\r\n", err? err : "NULL reply");
7433 if (err) zfree(err);
7434 success = 0;
7435 goto cleanup;
7436 }
7437 assert(function_list_reply->type == REDIS_REPLY_ARRAY);
7438 if (function_list_reply->elements > 0) {
7439 clusterManagerLogErr(">>> New node already contains functions and can not be added to the cluster. Use FUNCTION FLUSH and try again.\r\n");
7440 success = 0;
7441 goto cleanup;
7442 }
7443 clusterManagerLogInfo(">>> Send FUNCTION RESTORE to %s:%d\n", ip, port);
7444 function_restore_reply = CLUSTER_MANAGER_COMMAND(new_node, "FUNCTION RESTORE %b", reply->str, reply->len);
7445 if (!clusterManagerCheckRedisReply(new_node, function_restore_reply, &err)) {
7446 clusterManagerLogErr(">>> Failed loading functions to the new node (error = '%s')\r\n", err? err : "NULL reply");
7447 if (err) zfree(err);
7448 success = 0;
7449 goto cleanup;
7450 }
7451 }
7452 }
7453
7454 if (reply) freeReplyObject(reply);
7455
7456 // Send CLUSTER MEET command to the new node
7457 clusterManagerLogInfo(">>> Send CLUSTER MEET to node %s:%d to make it "
7458 "join the cluster.\n", ip, port);
7459 /* CLUSTER MEET requires an IP address, so we do a DNS lookup here. */
7460 char first_ip[NET_IP_STR_LEN];
7461 int anet_flags = ANET_NONE;
7462 if (config.prefer_ipv4) anet_flags |= ANET_PREFER_IPV4;
7463 if (config.prefer_ipv6) anet_flags |= ANET_PREFER_IPV6;
7464 if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), anet_flags) == ANET_ERR) {
7465 fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip);
7466 success = 0;
7467 goto cleanup;
7468 }
7469
7470 if (first->bus_port == 0 || (first->bus_port == first->port + CLUSTER_MANAGER_PORT_INCR)) {
7471 /* CLUSTER MEET bus-port parameter was added in 4.0.
7472 * So if (bus_port == 0) or (bus_port == port + CLUSTER_MANAGER_PORT_INCR),
7473 * we just call CLUSTER MEET with 2 arguments, using the old form. */
7474 reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d",
7475 first_ip, first->port);
7476 } else {
7477 reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d %d",
7478 first_ip, first->port, first->bus_port);
7479 }
7480
7481 if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL)))
7482 goto cleanup;
7483
7484 /* Additional configuration is needed if the node is added as a slave. */
7485 if (master_node) {
7486 sleep(1);
7487 clusterManagerWaitForClusterJoin();
7488 clusterManagerLogInfo(">>> Configure node as replica of %s:%d.\n",
7489 master_node->ip, master_node->port);
7490 freeReplyObject(reply);
7491 reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER REPLICATE %s",
7492 master_node->name);
7493 if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL)))
7494 goto cleanup;
7495 }
7496 clusterManagerLogOk("[OK] New node added correctly.\n");
7497cleanup:
7498 if (!added && new_node) freeClusterManagerNode(new_node);
7499 if (reply) freeReplyObject(reply);
7500 if (function_restore_reply) freeReplyObject(function_restore_reply);
7501 if (function_list_reply) freeReplyObject(function_list_reply);
7502 return success;
7503invalid_args:
7504 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
7505 return 0;
7506}
7507
7508static int clusterManagerCommandDeleteNode(int argc, char **argv) {
7509 UNUSED(argc);
7510 int success = 1;
7511 int port = 0;
7512 char *ip = NULL;
7513 if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
7514 char *node_id = argv[1];
7515 clusterManagerLogInfo(">>> Removing node %s from cluster %s:%d\n",
7516 node_id, ip, port);
7517 clusterManagerNode *ref_node = clusterManagerNewNode(ip, port, 0);
7518 clusterManagerNode *node = NULL;
7519
7520 // Load cluster information
7521 if (!clusterManagerLoadInfoFromNode(ref_node)) return 0;
7522
7523 // Check if the node exists and is not empty
7524 node = clusterManagerNodeByName(node_id);
7525 if (node == NULL) {
7526 clusterManagerLogErr("[ERR] No such node ID %s\n", node_id);
7527 return 0;
7528 }
7529 if (node->slots_count != 0) {
7530 clusterManagerLogErr("[ERR] Node %s:%d is not empty! Reshard data "
7531 "away and try again.\n", node->ip, node->port);
7532 return 0;
7533 }
7534
7535 // Send CLUSTER FORGET to all the nodes but the node to remove
7536 clusterManagerLogInfo(">>> Sending CLUSTER FORGET messages to the "
7537 "cluster...\n");
7538 listIter li;
7539 listNode *ln;
7540 listRewind(cluster_manager.nodes, &li);
7541 while ((ln = listNext(&li)) != NULL) {
7542 clusterManagerNode *n = ln->value;
7543 if (n == node) continue;
7544 if (n->replicate && !strcasecmp(n->replicate, node_id)) {
7545 // Reconfigure the slave to replicate with some other node
7546 clusterManagerNode *master = clusterManagerNodeWithLeastReplicas();
7547 assert(master != NULL);
7548 clusterManagerLogInfo(">>> %s:%d as replica of %s:%d\n",
7549 n->ip, n->port, master->ip, master->port);
7550 redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER REPLICATE %s",
7551 master->name);
7552 success = clusterManagerCheckRedisReply(n, r, NULL);
7553 if (r) freeReplyObject(r);
7554 if (!success) return 0;
7555 }
7556 redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER FORGET %s",
7557 node_id);
7558 success = clusterManagerCheckRedisReply(n, r, NULL);
7559 if (r) freeReplyObject(r);
7560 if (!success) return 0;
7561 }
7562
7563 /* Finally send CLUSTER RESET to the node. */
7564 clusterManagerLogInfo(">>> Sending CLUSTER RESET SOFT to the "
7565 "deleted node.\n");
7566 redisReply *r = redisCommand(node->context, "CLUSTER RESET %s", "SOFT");
7567 success = clusterManagerCheckRedisReply(node, r, NULL);
7568 if (r) freeReplyObject(r);
7569 return success;
7570invalid_args:
7571 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
7572 return 0;
7573}
7574
7575static int clusterManagerCommandInfo(int argc, char **argv) {
7576 int port = 0;
7577 char *ip = NULL;
7578 if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
7579 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
7580 if (!clusterManagerLoadInfoFromNode(node)) return 0;
7581 clusterManagerShowClusterInfo();
7582 return 1;
7583invalid_args:
7584 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
7585 return 0;
7586}
7587
7588static int clusterManagerCommandCheck(int argc, char **argv) {
7589 int port = 0;
7590 char *ip = NULL;
7591 if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
7592 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
7593 if (!clusterManagerLoadInfoFromNode(node)) return 0;
7594 clusterManagerShowClusterInfo();
7595 return clusterManagerCheckCluster(0);
7596invalid_args:
7597 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
7598 return 0;
7599}
7600
7601static int clusterManagerCommandFix(int argc, char **argv) {
7602 config.cluster_manager_command.flags |= CLUSTER_MANAGER_CMD_FLAG_FIX;
7603 return clusterManagerCommandCheck(argc, argv);
7604}
7605
7606static int clusterManagerCommandReshard(int argc, char **argv) {
7607 int port = 0;
7608 char *ip = NULL;
7609 if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
7610 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
7611 if (!clusterManagerLoadInfoFromNode(node)) return 0;
7612 clusterManagerCheckCluster(0);
7613 if (cluster_manager.errors && listLength(cluster_manager.errors) > 0) {
7614 fflush(stdout);
7615 fprintf(stderr,
7616 "*** Please fix your cluster problems before resharding\n");
7617 return 0;
7618 }
7619 int slots = config.cluster_manager_command.slots;
7620 if (!slots) {
7621 while (slots <= 0 || slots > CLUSTER_MANAGER_SLOTS) {
7622 printf("How many slots do you want to move (from 1 to %d)? ",
7623 CLUSTER_MANAGER_SLOTS);
7624 fflush(stdout);
7625 char buf[6];
7626 int nread = read(fileno(stdin),buf,6);
7627 if (nread <= 0) continue;
7628 int last_idx = nread - 1;
7629 if (buf[last_idx] != '\n') {
7630 int ch;
7631 while ((ch = getchar()) != '\n' && ch != EOF) {}
7632 }
7633 buf[last_idx] = '\0';
7634 slots = atoi(buf);
7635 }
7636 }
7637 char buf[255];
7638 char *to = config.cluster_manager_command.to,
7639 *from = config.cluster_manager_command.from;
7640 while (to == NULL) {
7641 printf("What is the receiving node ID? ");
7642 fflush(stdout);
7643 int nread = read(fileno(stdin),buf,255);
7644 if (nread <= 0) continue;
7645 int last_idx = nread - 1;
7646 if (buf[last_idx] != '\n') {
7647 int ch;
7648 while ((ch = getchar()) != '\n' && ch != EOF) {}
7649 }
7650 buf[last_idx] = '\0';
7651 if (strlen(buf) > 0) to = buf;
7652 }
7653 int raise_err = 0;
7654 clusterManagerNode *target = clusterNodeForResharding(to, NULL, &raise_err);
7655 if (target == NULL) return 0;
7656 list *sources = listCreate();
7657 list *table = NULL;
7658 int all = 0, result = 1;
7659 if (from == NULL) {
7660 printf("Please enter all the source node IDs.\n");
7661 printf(" Type 'all' to use all the nodes as source nodes for "
7662 "the hash slots.\n");
7663 printf(" Type 'done' once you entered all the source nodes IDs.\n");
7664 while (1) {
7665 printf("Source node #%lu: ", listLength(sources) + 1);
7666 fflush(stdout);
7667 int nread = read(fileno(stdin),buf,255);
7668 if (nread <= 0) continue;
7669 int last_idx = nread - 1;
7670 if (buf[last_idx] != '\n') {
7671 int ch;
7672 while ((ch = getchar()) != '\n' && ch != EOF) {}
7673 }
7674 buf[last_idx] = '\0';
7675 if (!strcmp(buf, "done")) break;
7676 else if (!strcmp(buf, "all")) {
7677 all = 1;
7678 break;
7679 } else {
7680 clusterManagerNode *src =
7681 clusterNodeForResharding(buf, target, &raise_err);
7682 if (src != NULL) listAddNodeTail(sources, src);
7683 else if (raise_err) {
7684 result = 0;
7685 goto cleanup;
7686 }
7687 }
7688 }
7689 } else {
7690 char *p;
7691 while((p = strchr(from, ',')) != NULL) {
7692 *p = '\0';
7693 if (!strcmp(from, "all")) {
7694 all = 1;
7695 break;
7696 } else {
7697 clusterManagerNode *src =
7698 clusterNodeForResharding(from, target, &raise_err);
7699 if (src != NULL) listAddNodeTail(sources, src);
7700 else if (raise_err) {
7701 result = 0;
7702 goto cleanup;
7703 }
7704 }
7705 from = p + 1;
7706 }
7707 /* Check if there's still another source to process. */
7708 if (!all && strlen(from) > 0) {
7709 if (!strcmp(from, "all")) all = 1;
7710 if (!all) {
7711 clusterManagerNode *src =
7712 clusterNodeForResharding(from, target, &raise_err);
7713 if (src != NULL) listAddNodeTail(sources, src);
7714 else if (raise_err) {
7715 result = 0;
7716 goto cleanup;
7717 }
7718 }
7719 }
7720 }
7721 listIter li;
7722 listNode *ln;
7723 if (all) {
7724 listEmpty(sources);
7725 listRewind(cluster_manager.nodes, &li);
7726 while ((ln = listNext(&li)) != NULL) {
7727 clusterManagerNode *n = ln->value;
7728 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
7729 continue;
7730 if (!sdscmp(n->name, target->name)) continue;
7731 listAddNodeTail(sources, n);
7732 }
7733 }
7734 if (listLength(sources) == 0) {
7735 fprintf(stderr, "*** No source nodes given, operation aborted.\n");
7736 result = 0;
7737 goto cleanup;
7738 }
7739 printf("\nReady to move %d slots.\n", slots);
7740 printf(" Source nodes:\n");
7741 listRewind(sources, &li);
7742 while ((ln = listNext(&li)) != NULL) {
7743 clusterManagerNode *src = ln->value;
7744 sds info = clusterManagerNodeInfo(src, 4);
7745 printf("%s\n", info);
7746 sdsfree(info);
7747 }
7748 printf(" Destination node:\n");
7749 sds info = clusterManagerNodeInfo(target, 4);
7750 printf("%s\n", info);
7751 sdsfree(info);
7752 table = clusterManagerComputeReshardTable(sources, slots);
7753 printf(" Resharding plan:\n");
7754 clusterManagerShowReshardTable(table);
7755 if (!(config.cluster_manager_command.flags &
7756 CLUSTER_MANAGER_CMD_FLAG_YES))
7757 {
7758 printf("Do you want to proceed with the proposed "
7759 "reshard plan (yes/no)? ");
7760 fflush(stdout);
7761 char buf[4];
7762 int nread = read(fileno(stdin),buf,4);
7763 buf[3] = '\0';
7764 if (nread <= 0 || strcmp("yes", buf) != 0) {
7765 result = 0;
7766 goto cleanup;
7767 }
7768 }
7769 int opts = CLUSTER_MANAGER_OPT_VERBOSE;
7770 listRewind(table, &li);
7771 while ((ln = listNext(&li)) != NULL) {
7772 clusterManagerReshardTableItem *item = ln->value;
7773 char *err = NULL;
7774 result = clusterManagerMoveSlot(item->source, target, item->slot,
7775 opts, &err);
7776 if (!result) {
7777 if (err != NULL) {
7778 clusterManagerLogErr("clusterManagerMoveSlot failed: %s\n", err);
7779 zfree(err);
7780 }
7781 goto cleanup;
7782 }
7783 }
7784cleanup:
7785 listRelease(sources);
7786 clusterManagerReleaseReshardTable(table);
7787 return result;
7788invalid_args:
7789 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
7790 return 0;
7791}
7792
7793static int clusterManagerCommandRebalance(int argc, char **argv) {
7794 int port = 0;
7795 char *ip = NULL;
7796 clusterManagerNode **weightedNodes = NULL;
7797 list *involved = NULL;
7798 if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
7799 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
7800 if (!clusterManagerLoadInfoFromNode(node)) return 0;
7801 int result = 1, i;
7802 if (config.cluster_manager_command.weight != NULL) {
7803 for (i = 0; i < config.cluster_manager_command.weight_argc; i++) {
7804 char *name = config.cluster_manager_command.weight[i];
7805 char *p = strchr(name, '=');
7806 if (p == NULL) {
7807 clusterManagerLogErr("*** invalid input %s\n", name);
7808 result = 0;
7809 goto cleanup;
7810 }
7811 *p = '\0';
7812 float w = atof(++p);
7813 clusterManagerNode *n = clusterManagerNodeByAbbreviatedName(name);
7814 if (n == NULL) {
7815 clusterManagerLogErr("*** No such master node %s\n", name);
7816 result = 0;
7817 goto cleanup;
7818 }
7819 n->weight = w;
7820 }
7821 }
7822 float total_weight = 0;
7823 int nodes_involved = 0;
7824 int use_empty = config.cluster_manager_command.flags &
7825 CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER;
7826 involved = listCreate();
7827 listIter li;
7828 listNode *ln;
7829 listRewind(cluster_manager.nodes, &li);
7830 /* Compute the total cluster weight. */
7831 while ((ln = listNext(&li)) != NULL) {
7832 clusterManagerNode *n = ln->value;
7833 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
7834 continue;
7835 if (!use_empty && n->slots_count == 0) {
7836 n->weight = 0;
7837 continue;
7838 }
7839 total_weight += n->weight;
7840 nodes_involved++;
7841 listAddNodeTail(involved, n);
7842 }
7843 weightedNodes = zmalloc(nodes_involved * sizeof(clusterManagerNode *));
7844 if (weightedNodes == NULL) goto cleanup;
7845 /* Check cluster, only proceed if it looks sane. */
7846 clusterManagerCheckCluster(1);
7847 if (cluster_manager.errors && listLength(cluster_manager.errors) > 0) {
7848 clusterManagerLogErr("*** Please fix your cluster problems "
7849 "before rebalancing\n");
7850 result = 0;
7851 goto cleanup;
7852 }
7853 /* Calculate the slots balance for each node. It's the number of
7854 * slots the node should lose (if positive) or gain (if negative)
7855 * in order to be balanced. */
7856 int threshold_reached = 0, total_balance = 0;
7857 float threshold = config.cluster_manager_command.threshold;
7858 i = 0;
7859 listRewind(involved, &li);
7860 while ((ln = listNext(&li)) != NULL) {
7861 clusterManagerNode *n = ln->value;
7862 weightedNodes[i++] = n;
7863 int expected = (int) (((float)CLUSTER_MANAGER_SLOTS / total_weight) *
7864 n->weight);
7865 n->balance = n->slots_count - expected;
7866 total_balance += n->balance;
7867 /* Compute the percentage of difference between the
7868 * expected number of slots and the real one, to see
7869 * if it's over the threshold specified by the user. */
7870 int over_threshold = 0;
7871 if (threshold > 0) {
7872 if (n->slots_count > 0) {
7873 float err_perc = fabs((100-(100.0*expected/n->slots_count)));
7874 if (err_perc > threshold) over_threshold = 1;
7875 } else if (expected > 1) {
7876 over_threshold = 1;
7877 }
7878 }
7879 if (over_threshold) threshold_reached = 1;
7880 }
7881 if (!threshold_reached) {
7882 clusterManagerLogWarn("*** No rebalancing needed! "
7883 "All nodes are within the %.2f%% threshold.\n",
7884 config.cluster_manager_command.threshold);
7885 goto cleanup;
7886 }
7887 /* Because of rounding, it is possible that the balance of all nodes
7888 * summed does not give 0. Make sure that nodes that have to provide
7889 * slots are always matched by nodes receiving slots. */
7890 while (total_balance > 0) {
7891 listRewind(involved, &li);
7892 while ((ln = listNext(&li)) != NULL) {
7893 clusterManagerNode *n = ln->value;
7894 if (n->balance <= 0 && total_balance > 0) {
7895 n->balance--;
7896 total_balance--;
7897 }
7898 }
7899 }
7900 /* Sort nodes by their slots balance. */
7901 qsort(weightedNodes, nodes_involved, sizeof(clusterManagerNode *),
7902 clusterManagerCompareNodeBalance);
7903 clusterManagerLogInfo(">>> Rebalancing across %d nodes. "
7904 "Total weight = %.2f\n",
7905 nodes_involved, total_weight);
7906 if (config.verbose) {
7907 for (i = 0; i < nodes_involved; i++) {
7908 clusterManagerNode *n = weightedNodes[i];
7909 printf("%s:%d balance is %d slots\n", n->ip, n->port, n->balance);
7910 }
7911 }
7912 /* Now we have at the start of the 'sn' array nodes that should get
7913 * slots, at the end nodes that must give slots.
7914 * We take two indexes, one at the start, and one at the end,
7915 * incrementing or decrementing the indexes accordingly til we
7916 * find nodes that need to get/provide slots. */
7917 int dst_idx = 0;
7918 int src_idx = nodes_involved - 1;
7919 int simulate = config.cluster_manager_command.flags &
7920 CLUSTER_MANAGER_CMD_FLAG_SIMULATE;
7921 while (dst_idx < src_idx) {
7922 clusterManagerNode *dst = weightedNodes[dst_idx];
7923 clusterManagerNode *src = weightedNodes[src_idx];
7924 int db = abs(dst->balance);
7925 int sb = abs(src->balance);
7926 int numslots = (db < sb ? db : sb);
7927 if (numslots > 0) {
7928 printf("Moving %d slots from %s:%d to %s:%d\n", numslots,
7929 src->ip,
7930 src->port,
7931 dst->ip,
7932 dst->port);
7933 /* Actually move the slots. */
7934 list *lsrc = listCreate(), *table = NULL;
7935 listAddNodeTail(lsrc, src);
7936 table = clusterManagerComputeReshardTable(lsrc, numslots);
7937 listRelease(lsrc);
7938 int table_len = (int) listLength(table);
7939 if (!table || table_len != numslots) {
7940 clusterManagerLogErr("*** Assertion failed: Reshard table "
7941 "!= number of slots");
7942 result = 0;
7943 goto end_move;
7944 }
7945 if (simulate) {
7946 for (i = 0; i < table_len; i++) printf("#");
7947 } else {
7948 int opts = CLUSTER_MANAGER_OPT_QUIET |
7949 CLUSTER_MANAGER_OPT_UPDATE;
7950 listRewind(table, &li);
7951 while ((ln = listNext(&li)) != NULL) {
7952 clusterManagerReshardTableItem *item = ln->value;
7953 char *err;
7954 result = clusterManagerMoveSlot(item->source,
7955 dst,
7956 item->slot,
7957 opts, &err);
7958 if (!result) {
7959 clusterManagerLogErr("*** clusterManagerMoveSlot: %s\n", err);
7960 zfree(err);
7961 goto end_move;
7962 }
7963 printf("#");
7964 fflush(stdout);
7965 }
7966
7967 }
7968 printf("\n");
7969end_move:
7970 clusterManagerReleaseReshardTable(table);
7971 if (!result) goto cleanup;
7972 }
7973 /* Update nodes balance. */
7974 dst->balance += numslots;
7975 src->balance -= numslots;
7976 if (dst->balance == 0) dst_idx++;
7977 if (src->balance == 0) src_idx --;
7978 }
7979cleanup:
7980 if (involved != NULL) listRelease(involved);
7981 if (weightedNodes != NULL) zfree(weightedNodes);
7982 return result;
7983invalid_args:
7984 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
7985 return 0;
7986}
7987
7988static int clusterManagerCommandSetTimeout(int argc, char **argv) {
7989 UNUSED(argc);
7990 int port = 0;
7991 char *ip = NULL;
7992 if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
7993 int timeout = atoi(argv[1]);
7994 if (timeout < 100) {
7995 fprintf(stderr, "Setting a node timeout of less than 100 "
7996 "milliseconds is a bad idea.\n");
7997 return 0;
7998 }
7999 // Load cluster information
8000 clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
8001 if (!clusterManagerLoadInfoFromNode(node)) return 0;
8002 int ok_count = 0, err_count = 0;
8003
8004 clusterManagerLogInfo(">>> Reconfiguring node timeout in every "
8005 "cluster node...\n");
8006 listIter li;
8007 listNode *ln;
8008 listRewind(cluster_manager.nodes, &li);
8009 while ((ln = listNext(&li)) != NULL) {
8010 clusterManagerNode *n = ln->value;
8011 char *err = NULL;
8012 redisReply *reply = CLUSTER_MANAGER_COMMAND(n, "CONFIG %s %s %d",
8013 "SET",
8014 "cluster-node-timeout",
8015 timeout);
8016 if (reply == NULL) goto reply_err;
8017 int ok = clusterManagerCheckRedisReply(n, reply, &err);
8018 freeReplyObject(reply);
8019 if (!ok) goto reply_err;
8020 reply = CLUSTER_MANAGER_COMMAND(n, "CONFIG %s", "REWRITE");
8021 if (reply == NULL) goto reply_err;
8022 ok = clusterManagerCheckRedisReply(n, reply, &err);
8023 freeReplyObject(reply);
8024 if (!ok) goto reply_err;
8025 clusterManagerLogWarn("*** New timeout set for %s:%d\n", n->ip,
8026 n->port);
8027 ok_count++;
8028 continue;
8029reply_err:;
8030 int need_free = 0;
8031 if (err == NULL) err = "";
8032 else need_free = 1;
8033 clusterManagerLogErr("ERR setting node-timeout for %s:%d: %s\n", n->ip,
8034 n->port, err);
8035 if (need_free) zfree(err);
8036 err_count++;
8037 }
8038 clusterManagerLogInfo(">>> New node timeout set. %d OK, %d ERR.\n",
8039 ok_count, err_count);
8040 return 1;
8041invalid_args:
8042 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
8043 return 0;
8044}
8045
8046static int clusterManagerCommandImport(int argc, char **argv) {
8047 int success = 1;
8048 int port = 0, src_port = 0;
8049 char *ip = NULL, *src_ip = NULL;
8050 char *invalid_args_msg = NULL;
8051 sds cmdfmt = NULL;
8052 if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) {
8053 invalid_args_msg = CLUSTER_MANAGER_INVALID_HOST_ARG;
8054 goto invalid_args;
8055 }
8056 if (config.cluster_manager_command.from == NULL) {
8057 invalid_args_msg = "[ERR] Option '--cluster-from' is required for "
8058 "subcommand 'import'.\n";
8059 goto invalid_args;
8060 }
8061 char *src_host[] = {config.cluster_manager_command.from};
8062 if (!getClusterHostFromCmdArgs(1, src_host, &src_ip, &src_port)) {
8063 invalid_args_msg = "[ERR] Invalid --cluster-from host. You need to "
8064 "pass a valid address (ie. 120.0.0.1:7000).\n";
8065 goto invalid_args;
8066 }
8067 clusterManagerLogInfo(">>> Importing data from %s:%d to cluster %s:%d\n",
8068 src_ip, src_port, ip, port);
8069
8070 clusterManagerNode *refnode = clusterManagerNewNode(ip, port, 0);
8071 if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
8072 if (!clusterManagerCheckCluster(0)) return 0;
8073 char *reply_err = NULL;
8074 redisReply *src_reply = NULL;
8075 // Connect to the source node.
8076 redisContext *src_ctx = redisConnectWrapper(src_ip, src_port, config.connect_timeout);
8077 if (src_ctx->err) {
8078 success = 0;
8079 fprintf(stderr,"Could not connect to Redis at %s:%d: %s.\n", src_ip,
8080 src_port, src_ctx->errstr);
8081 goto cleanup;
8082 }
8083 // Auth for the source node.
8084 char *from_user = config.cluster_manager_command.from_user;
8085 char *from_pass = config.cluster_manager_command.from_pass;
8086 if (cliAuth(src_ctx, from_user, from_pass) == REDIS_ERR) {
8087 success = 0;
8088 goto cleanup;
8089 }
8090
8091 src_reply = reconnectingRedisCommand(src_ctx, "INFO");
8092 if (!src_reply || src_reply->type == REDIS_REPLY_ERROR) {
8093 if (src_reply && src_reply->str) reply_err = src_reply->str;
8094 success = 0;
8095 goto cleanup;
8096 }
8097 if (getLongInfoField(src_reply->str, "cluster_enabled")) {
8098 clusterManagerLogErr("[ERR] The source node should not be a "
8099 "cluster node.\n");
8100 success = 0;
8101 goto cleanup;
8102 }
8103 freeReplyObject(src_reply);
8104 src_reply = reconnectingRedisCommand(src_ctx, "DBSIZE");
8105 if (!src_reply || src_reply->type == REDIS_REPLY_ERROR) {
8106 if (src_reply && src_reply->str) reply_err = src_reply->str;
8107 success = 0;
8108 goto cleanup;
8109 }
8110 int size = src_reply->integer, i;
8111 clusterManagerLogWarn("*** Importing %d keys from DB 0\n", size);
8112
8113 // Build a slot -> node map
8114 clusterManagerNode *slots_map[CLUSTER_MANAGER_SLOTS];
8115 memset(slots_map, 0, sizeof(slots_map));
8116 listIter li;
8117 listNode *ln;
8118 for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
8119 listRewind(cluster_manager.nodes, &li);
8120 while ((ln = listNext(&li)) != NULL) {
8121 clusterManagerNode *n = ln->value;
8122 if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
8123 if (n->slots_count == 0) continue;
8124 if (n->slots[i]) {
8125 slots_map[i] = n;
8126 break;
8127 }
8128 }
8129 }
8130 cmdfmt = sdsnew("MIGRATE %s %d %s %d %d");
8131 if (config.conn_info.auth) {
8132 if (config.conn_info.user) {
8133 cmdfmt = sdscatfmt(cmdfmt," AUTH2 %s %s", config.conn_info.user, config.conn_info.auth);
8134 } else {
8135 cmdfmt = sdscatfmt(cmdfmt," AUTH %s", config.conn_info.auth);
8136 }
8137 }
8138
8139 if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_COPY)
8140 cmdfmt = sdscat(cmdfmt," COPY");
8141 if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_REPLACE)
8142 cmdfmt = sdscat(cmdfmt," REPLACE");
8143
8144 /* Use SCAN to iterate over the keys, migrating to the
8145 * right node as needed. */
8146 int cursor = -999, timeout = config.cluster_manager_command.timeout;
8147 while (cursor != 0) {
8148 if (cursor < 0) cursor = 0;
8149 freeReplyObject(src_reply);
8150 src_reply = reconnectingRedisCommand(src_ctx, "SCAN %d COUNT %d",
8151 cursor, 1000);
8152 if (!src_reply || src_reply->type == REDIS_REPLY_ERROR) {
8153 if (src_reply && src_reply->str) reply_err = src_reply->str;
8154 success = 0;
8155 goto cleanup;
8156 }
8157 assert(src_reply->type == REDIS_REPLY_ARRAY);
8158 assert(src_reply->elements >= 2);
8159 assert(src_reply->element[1]->type == REDIS_REPLY_ARRAY);
8160 if (src_reply->element[0]->type == REDIS_REPLY_STRING)
8161 cursor = atoi(src_reply->element[0]->str);
8162 else if (src_reply->element[0]->type == REDIS_REPLY_INTEGER)
8163 cursor = src_reply->element[0]->integer;
8164 int keycount = src_reply->element[1]->elements;
8165 for (i = 0; i < keycount; i++) {
8166 redisReply *kr = src_reply->element[1]->element[i];
8167 assert(kr->type == REDIS_REPLY_STRING);
8168 char *key = kr->str;
8169 uint16_t slot = clusterManagerKeyHashSlot(key, kr->len);
8170 clusterManagerNode *target = slots_map[slot];
8171 printf("Migrating %s to %s:%d: ", key, target->ip, target->port);
8172 redisReply *r = reconnectingRedisCommand(src_ctx, cmdfmt,
8173 target->ip, target->port,
8174 key, 0, timeout);
8175 if (!r || r->type == REDIS_REPLY_ERROR) {
8176 if (r && r->str) {
8177 clusterManagerLogErr("Source %s:%d replied with "
8178 "error:\n%s\n", src_ip, src_port,
8179 r->str);
8180 }
8181 success = 0;
8182 }
8183 freeReplyObject(r);
8184 if (!success) goto cleanup;
8185 clusterManagerLogOk("OK\n");
8186 }
8187 }
8188cleanup:
8189 if (reply_err)
8190 clusterManagerLogErr("Source %s:%d replied with error:\n%s\n",
8191 src_ip, src_port, reply_err);
8192 if (src_ctx) redisFree(src_ctx);
8193 if (src_reply) freeReplyObject(src_reply);
8194 if (cmdfmt) sdsfree(cmdfmt);
8195 return success;
8196invalid_args:
8197 fprintf(stderr, "%s", invalid_args_msg);
8198 return 0;
8199}
8200
8201static int clusterManagerCommandCall(int argc, char **argv) {
8202 int port = 0, i;
8203 char *ip = NULL;
8204 if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
8205 clusterManagerNode *refnode = clusterManagerNewNode(ip, port, 0);
8206 if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
8207 argc--;
8208 argv++;
8209 size_t *argvlen = zmalloc(argc*sizeof(size_t));
8210 clusterManagerLogInfo(">>> Calling");
8211 for (i = 0; i < argc; i++) {
8212 argvlen[i] = strlen(argv[i]);
8213 printf(" %s", argv[i]);
8214 }
8215 printf("\n");
8216 listIter li;
8217 listNode *ln;
8218 listRewind(cluster_manager.nodes, &li);
8219 while ((ln = listNext(&li)) != NULL) {
8220 clusterManagerNode *n = ln->value;
8221 if ((config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_MASTERS_ONLY)
8222 && (n->replicate != NULL)) continue; // continue if node is slave
8223 if ((config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_SLAVES_ONLY)
8224 && (n->replicate == NULL)) continue; // continue if node is master
8225 if (!n->context && !clusterManagerNodeConnect(n)) continue;
8226 redisReply *reply = NULL;
8227 redisAppendCommandArgv(n->context, argc, (const char **) argv, argvlen);
8228 int status = redisGetReply(n->context, (void **)(&reply));
8229 if (status != REDIS_OK || reply == NULL )
8230 printf("%s:%d: Failed!\n", n->ip, n->port);
8231 else {
8232 sds formatted_reply = cliFormatReplyRaw(reply);
8233 printf("%s:%d: %s\n", n->ip, n->port, (char *) formatted_reply);
8234 sdsfree(formatted_reply);
8235 }
8236 if (reply != NULL) freeReplyObject(reply);
8237 }
8238 zfree(argvlen);
8239 return 1;
8240invalid_args:
8241 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
8242 return 0;
8243}
8244
8245static int clusterManagerCommandBackup(int argc, char **argv) {
8246 UNUSED(argc);
8247 int success = 1, port = 0;
8248 char *ip = NULL;
8249 sds json = NULL;
8250 sds jsonpath = NULL;
8251
8252 if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
8253 clusterManagerNode *refnode = clusterManagerNewNode(ip, port, 0);
8254 if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
8255 int no_issues = clusterManagerCheckCluster(0);
8256 int cluster_errors_count = (no_issues ? 0 :
8257 listLength(cluster_manager.errors));
8258 config.cluster_manager_command.backup_dir = argv[1];
8259
8260 struct stat st = {0};
8261 char *backup_dir = config.cluster_manager_command.backup_dir;
8262
8263 if (stat(backup_dir, &st) == -1) {
8264 if (errno == ENOENT) {
8265 clusterManagerLogErr("[ERR] The specified backup directory '%s' does not exist.\n", backup_dir);
8266 } else {
8267 clusterManagerLogErr("[ERR] Cannot stat backup directory %s: %s\n",
8268 backup_dir, strerror(errno));
8269 }
8270 success = 0;
8271 goto cleanup;
8272 } else if (!S_ISDIR(st.st_mode)) {
8273 clusterManagerLogErr("[ERR] The specified backup path '%s' exists but is not a directory.\n", backup_dir);
8274 success = 0;
8275 goto cleanup;
8276 }
8277
8278 json = sdsnew("[\n");
8279 int first_node = 0;
8280 listIter li;
8281 listNode *ln;
8282 listRewind(cluster_manager.nodes, &li);
8283 while ((ln = listNext(&li)) != NULL) {
8284 if (!first_node) first_node = 1;
8285 else json = sdscat(json, ",\n");
8286 clusterManagerNode *node = ln->value;
8287 sds node_json = clusterManagerNodeGetJSON(node, cluster_errors_count);
8288 json = sdscat(json, node_json);
8289 sdsfree(node_json);
8290 if (node->replicate)
8291 continue;
8292 clusterManagerLogInfo(">>> Node %s:%d -> Saving RDB...\n",
8293 node->ip, node->port);
8294 fflush(stdout);
8295 getRDB(node);
8296 }
8297 json = sdscat(json, "\n]");
8298 jsonpath = sdsnew(config.cluster_manager_command.backup_dir);
8299 if (jsonpath[sdslen(jsonpath) - 1] != '/')
8300 jsonpath = sdscat(jsonpath, "/");
8301 jsonpath = sdscat(jsonpath, "nodes.json");
8302 fflush(stdout);
8303 clusterManagerLogInfo("Saving cluster configuration to: %s\n", jsonpath);
8304 FILE *out = fopen(jsonpath, "w+");
8305 if (!out) {
8306 clusterManagerLogErr("Could not save nodes to: %s\n", jsonpath);
8307 success = 0;
8308 goto cleanup;
8309 }
8310 fputs(json, out);
8311 fclose(out);
8312cleanup:
8313 sdsfree(json);
8314 sdsfree(jsonpath);
8315 if (success) {
8316 if (!no_issues) {
8317 clusterManagerLogWarn("*** Cluster seems to have some problems, "
8318 "please be aware of it if you're going "
8319 "to restore this backup.\n");
8320 }
8321 clusterManagerLogOk("[OK] Backup created into: %s\n",
8322 config.cluster_manager_command.backup_dir);
8323 } else clusterManagerLogOk("[ERR] Failed to back cluster!\n");
8324 return success;
8325invalid_args:
8326 fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
8327 return 0;
8328}
8329
8330static int clusterManagerCommandHelp(int argc, char **argv) {
8331 UNUSED(argc);
8332 UNUSED(argv);
8333 int commands_count = sizeof(clusterManagerCommands) /
8334 sizeof(clusterManagerCommandDef);
8335 int i = 0, j;
8336 fprintf(stdout, "Cluster Manager Commands:\n");
8337 int padding = 15;
8338 for (; i < commands_count; i++) {
8339 clusterManagerCommandDef *def = &(clusterManagerCommands[i]);
8340 int namelen = strlen(def->name), padlen = padding - namelen;
8341 fprintf(stdout, " %s", def->name);
8342 for (j = 0; j < padlen; j++) fprintf(stdout, " ");
8343 fprintf(stdout, "%s\n", (def->args ? def->args : ""));
8344 if (def->options != NULL) {
8345 int optslen = strlen(def->options);
8346 char *p = def->options, *eos = p + optslen;
8347 char *comma = NULL;
8348 while ((comma = strchr(p, ',')) != NULL) {
8349 int deflen = (int)(comma - p);
8350 char buf[255];
8351 memcpy(buf, p, deflen);
8352 buf[deflen] = '\0';
8353 for (j = 0; j < padding; j++) fprintf(stdout, " ");
8354 fprintf(stdout, " --cluster-%s\n", buf);
8355 p = comma + 1;
8356 if (p >= eos) break;
8357 }
8358 if (p < eos) {
8359 for (j = 0; j < padding; j++) fprintf(stdout, " ");
8360 fprintf(stdout, " --cluster-%s\n", p);
8361 }
8362 }
8363 }
8364 fprintf(stdout, "\nFor check, fix, reshard, del-node, set-timeout, "
8365 "info, rebalance, call, import, backup you "
8366 "can specify the host and port of any working node in "
8367 "the cluster.\n");
8368
8369 int options_count = sizeof(clusterManagerOptions) /
8370 sizeof(clusterManagerOptionDef);
8371 i = 0;
8372 fprintf(stdout, "\nCluster Manager Options:\n");
8373 for (; i < options_count; i++) {
8374 clusterManagerOptionDef *def = &(clusterManagerOptions[i]);
8375 int namelen = strlen(def->name), padlen = padding - namelen;
8376 fprintf(stdout, " %s", def->name);
8377 for (j = 0; j < padlen; j++) fprintf(stdout, " ");
8378 fprintf(stdout, "%s\n", def->desc);
8379 }
8380
8381 fprintf(stdout, "\n");
8382 return 0;
8383}
8384
8385/*------------------------------------------------------------------------------
8386 * Latency and latency history modes
8387 *--------------------------------------------------------------------------- */
8388
8389static void latencyModePrint(long long min, long long max, double avg, long long count) {
8390 if (config.output == OUTPUT_STANDARD) {
8391 printf("min: %lld, max: %lld, avg: %.2f (%lld samples)",
8392 min, max, avg, count);
8393 fflush(stdout);
8394 } else if (config.output == OUTPUT_CSV) {
8395 printf("%lld,%lld,%.2f,%lld\n", min, max, avg, count);
8396 } else if (config.output == OUTPUT_RAW) {
8397 printf("%lld %lld %.2f %lld\n", min, max, avg, count);
8398 } else if (config.output == OUTPUT_JSON) {
8399 printf("{\"min\": %lld, \"max\": %lld, \"avg\": %.2f, \"count\": %lld}\n", min, max, avg, count);
8400 }
8401}
8402
8403#define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
8404#define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
8405static void latencyMode(void) {
8406 redisReply *reply;
8407 long long start, latency, min = 0, max = 0, tot = 0, count = 0;
8408 long long history_interval =
8409 config.interval ? config.interval/1000 :
8410 LATENCY_HISTORY_DEFAULT_INTERVAL;
8411 double avg;
8412 long long history_start = mstime();
8413
8414 /* Set a default for the interval in case of --latency option
8415 * with --raw, --csv or when it is redirected to non tty. */
8416 if (config.interval == 0) {
8417 config.interval = 1000;
8418 } else {
8419 config.interval /= 1000; /* We need to convert to milliseconds. */
8420 }
8421
8422 if (!context) exit(1);
8423 while(1) {
8424 start = mstime();
8425 reply = reconnectingRedisCommand(context,"PING");
8426 if (reply == NULL) {
8427 fprintf(stderr,"\nI/O error\n");
8428 exit(1);
8429 }
8430 latency = mstime()-start;
8431 freeReplyObject(reply);
8432 count++;
8433 if (count == 1) {
8434 min = max = tot = latency;
8435 avg = (double) latency;
8436 } else {
8437 if (latency < min) min = latency;
8438 if (latency > max) max = latency;
8439 tot += latency;
8440 avg = (double) tot/count;
8441 }
8442
8443 if (config.output == OUTPUT_STANDARD) {
8444 printf("\x1b[0G\x1b[2K"); /* Clear the line. */
8445 latencyModePrint(min,max,avg,count);
8446 } else {
8447 if (config.latency_history) {
8448 latencyModePrint(min,max,avg,count);
8449 } else if (mstime()-history_start > config.interval) {
8450 latencyModePrint(min,max,avg,count);
8451 exit(0);
8452 }
8453 }
8454
8455 if (config.latency_history && mstime()-history_start > history_interval)
8456 {
8457 printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000);
8458 history_start = mstime();
8459 min = max = tot = count = 0;
8460 }
8461 usleep(LATENCY_SAMPLE_RATE * 1000);
8462 }
8463}
8464
8465/*------------------------------------------------------------------------------
8466 * Latency distribution mode -- requires 256 colors xterm
8467 *--------------------------------------------------------------------------- */
8468
8469#define LATENCY_DIST_DEFAULT_INTERVAL 1000 /* milliseconds. */
8470
8471/* Structure to store samples distribution. */
8472struct distsamples {
8473 long long max; /* Max latency to fit into this interval (usec). */
8474 long long count; /* Number of samples in this interval. */
8475 int character; /* Associated character in visualization. */
8476};
8477
8478/* Helper function for latencyDistMode(). Performs the spectrum visualization
8479 * of the collected samples targeting an xterm 256 terminal.
8480 *
8481 * Takes an array of distsamples structures, ordered from smaller to bigger
8482 * 'max' value. Last sample max must be 0, to mean that it holds all the
8483 * samples greater than the previous one, and is also the stop sentinel.
8484 *
8485 * "tot' is the total number of samples in the different buckets, so it
8486 * is the SUM(samples[i].count) for i to 0 up to the max sample.
8487 *
8488 * As a side effect the function sets all the buckets count to 0. */
8489void showLatencyDistSamples(struct distsamples *samples, long long tot) {
8490 int j;
8491
8492 /* We convert samples into an index inside the palette
8493 * proportional to the percentage a given bucket represents.
8494 * This way intensity of the different parts of the spectrum
8495 * don't change relative to the number of requests, which avoids to
8496 * pollute the visualization with non-latency related info. */
8497 printf("\033[38;5;0m"); /* Set foreground color to black. */
8498 for (j = 0; ; j++) {
8499 int coloridx =
8500 ceil((double) samples[j].count / tot * (spectrum_palette_size-1));
8501 int color = spectrum_palette[coloridx];
8502 printf("\033[48;5;%dm%c", (int)color, samples[j].character);
8503 samples[j].count = 0;
8504 if (samples[j].max == 0) break; /* Last sample. */
8505 }
8506 printf("\033[0m\n");
8507 fflush(stdout);
8508}
8509
8510/* Show the legend: different buckets values and colors meaning, so
8511 * that the spectrum is more easily readable. */
8512void showLatencyDistLegend(void) {
8513 int j;
8514
8515 printf("---------------------------------------------\n");
8516 printf(". - * # .01 .125 .25 .5 milliseconds\n");
8517 printf("1,2,3,...,9 from 1 to 9 milliseconds\n");
8518 printf("A,B,C,D,E 10,20,30,40,50 milliseconds\n");
8519 printf("F,G,H,I,J .1,.2,.3,.4,.5 seconds\n");
8520 printf("K,L,M,N,O,P,Q,? 1,2,4,8,16,30,60,>60 seconds\n");
8521 printf("From 0 to 100%%: ");
8522 for (j = 0; j < spectrum_palette_size; j++) {
8523 printf("\033[48;5;%dm ", spectrum_palette[j]);
8524 }
8525 printf("\033[0m\n");
8526 printf("---------------------------------------------\n");
8527}
8528
8529static void latencyDistMode(void) {
8530 redisReply *reply;
8531 long long start, latency, count = 0;
8532 long long history_interval =
8533 config.interval ? config.interval/1000 :
8534 LATENCY_DIST_DEFAULT_INTERVAL;
8535 long long history_start = ustime();
8536 int j, outputs = 0;
8537
8538 struct distsamples samples[] = {
8539 /* We use a mostly logarithmic scale, with certain linear intervals
8540 * which are more interesting than others, like 1-10 milliseconds
8541 * range. */
8542 {10,0,'.'}, /* 0.01 ms */
8543 {125,0,'-'}, /* 0.125 ms */
8544 {250,0,'*'}, /* 0.25 ms */
8545 {500,0,'#'}, /* 0.5 ms */
8546 {1000,0,'1'}, /* 1 ms */
8547 {2000,0,'2'}, /* 2 ms */
8548 {3000,0,'3'}, /* 3 ms */
8549 {4000,0,'4'}, /* 4 ms */
8550 {5000,0,'5'}, /* 5 ms */
8551 {6000,0,'6'}, /* 6 ms */
8552 {7000,0,'7'}, /* 7 ms */
8553 {8000,0,'8'}, /* 8 ms */
8554 {9000,0,'9'}, /* 9 ms */
8555 {10000,0,'A'}, /* 10 ms */
8556 {20000,0,'B'}, /* 20 ms */
8557 {30000,0,'C'}, /* 30 ms */
8558 {40000,0,'D'}, /* 40 ms */
8559 {50000,0,'E'}, /* 50 ms */
8560 {100000,0,'F'}, /* 0.1 s */
8561 {200000,0,'G'}, /* 0.2 s */
8562 {300000,0,'H'}, /* 0.3 s */
8563 {400000,0,'I'}, /* 0.4 s */
8564 {500000,0,'J'}, /* 0.5 s */
8565 {1000000,0,'K'}, /* 1 s */
8566 {2000000,0,'L'}, /* 2 s */
8567 {4000000,0,'M'}, /* 4 s */
8568 {8000000,0,'N'}, /* 8 s */
8569 {16000000,0,'O'}, /* 16 s */
8570 {30000000,0,'P'}, /* 30 s */
8571 {60000000,0,'Q'}, /* 1 minute */
8572 {0,0,'?'}, /* > 1 minute */
8573 };
8574
8575 if (!context) exit(1);
8576 while(1) {
8577 start = ustime();
8578 reply = reconnectingRedisCommand(context,"PING");
8579 if (reply == NULL) {
8580 fprintf(stderr,"\nI/O error\n");
8581 exit(1);
8582 }
8583 latency = ustime()-start;
8584 freeReplyObject(reply);
8585 count++;
8586
8587 /* Populate the relevant bucket. */
8588 for (j = 0; ; j++) {
8589 if (samples[j].max == 0 || latency <= samples[j].max) {
8590 samples[j].count++;
8591 break;
8592 }
8593 }
8594
8595 /* From time to time show the spectrum. */
8596 if (count && (ustime()-history_start)/1000 > history_interval) {
8597 if ((outputs++ % 20) == 0)
8598 showLatencyDistLegend();
8599 showLatencyDistSamples(samples,count);
8600 history_start = ustime();
8601 count = 0;
8602 }
8603 usleep(LATENCY_SAMPLE_RATE * 1000);
8604 }
8605}
8606
8607/*------------------------------------------------------------------------------
8608 * Vset recall mode.
8609 *
8610 * This mode targets a specific vector set key, performing queries on
8611 * vectors composed mixing components from existing vectors (each component of
8612 * the query vector is the component of a random source vector), then uses
8613 * VSIM and VSIM TRUTH to test for recall percentage.
8614 *--------------------------------------------------------------------------- */
8615static void vsetRecallMode(void) {
8616 redisReply *reply, *vsim_reply, *truth_reply;
8617 int ele_count = config.vset_recall_ele_count;
8618 int vsim_count = config.vset_recall_vsim_count;
8619 int vsim_ef = config.vset_recall_vsim_ef;
8620 unsigned long long queries = 0, total_overlap = 0;
8621 long long refresh_time = mstime();
8622 struct hdr_histogram *recall_histogram;
8623
8624 if (!context) exit(1);
8625
8626 /* HDR histogram requires minimum value >= 1 for some reason.
8627 * We store recall percentages as:
8628 * (recall% * 100) + 1, giving us range 1 to 10001.
8629 * This maps: 0.00% -> 1, 50.00% -> 5001, 100.00% -> 10001
8630 * Precision: 2 significant figures = 0.01% accuracy. */
8631 if (hdr_init(1, 10001, 2, &recall_histogram)) {
8632 fprintf(stderr, "Failed to initialize recall histogram\n");
8633 exit(1);
8634 }
8635
8636 /* Get vector dimension. */
8637 reply = reconnectingRedisCommand(context, "VDIM %s",
8638 config.vset_recall_key);
8639 if (reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
8640 fprintf(stderr, "Error: Cannot get dimension for key %s\n",
8641 config.vset_recall_key);
8642 exit(1);
8643 }
8644 unsigned int dim = reply->integer;
8645 freeReplyObject(reply);
8646
8647 printf("\n# Testing recall for vector set: %s (dimension: %d)\n",
8648 config.vset_recall_key, dim);
8649 printf("# Mixing %d random element vectors, top %d results, EF=%d\n\n",
8650 ele_count, vsim_count, vsim_ef);
8651
8652 signal(SIGINT, longStatLoopModeStop);
8653
8654 /* Do the same recall test again and again. */
8655 while (force_cancel_loop == 0) {
8656 /* Get random members. */
8657 reply = reconnectingRedisCommand(context, "VRANDMEMBER %s %d",
8658 config.vset_recall_key, ele_count);
8659 if (reply == NULL || reply->type != REDIS_REPLY_ARRAY ||
8660 reply->elements == 0) {
8661 fprintf(stderr, "Error fetching random members\n");
8662 exit(1);
8663 }
8664
8665 /* Fetch and store vectors. */
8666 double **vectors = zmalloc(reply->elements * sizeof(double*));
8667 int valid_vectors = 0;
8668
8669 for (size_t i = 0; i < reply->elements; i++) {
8670 redisReply *vemb = reconnectingRedisCommand(context, "VEMB %s %s",
8671 config.vset_recall_key,
8672 reply->element[i]->str);
8673 if (vemb && vemb->type == REDIS_REPLY_ARRAY &&
8674 vemb->elements == dim) {
8675 vectors[valid_vectors] = zmalloc(dim * sizeof(double));
8676 for (unsigned int j = 0; j < dim; j++) {
8677 vectors[valid_vectors][j] = atof(vemb->element[j]->str);
8678 }
8679 valid_vectors++;
8680 }
8681 if (vemb) freeReplyObject(vemb);
8682 }
8683 freeReplyObject(reply);
8684
8685 if (valid_vectors == 0) {
8686 fprintf(stderr, "No valid vectors retrieved\n");
8687 zfree(vectors);
8688 continue;
8689 }
8690
8691 /* Create mixed query vector by randomly selecting components. */
8692 float *query = zmalloc(sizeof(float) * dim);
8693 for (unsigned int i = 0; i < dim; i++) {
8694 int src = rand() % valid_vectors;
8695 query[i] = vectors[src][i];
8696 }
8697
8698 for (int i = 0; i < valid_vectors; i++) zfree(vectors[i]);
8699 zfree(vectors);
8700
8701 /* Execute VSIM query with HNSW. */
8702 vsim_reply = reconnectingRedisCommand(context,
8703 "VSIM %s FP32 %b COUNT %d EF %d",
8704 config.vset_recall_key, query,
8705 sizeof(float)*dim, vsim_count, vsim_ef);
8706 if (vsim_reply == NULL || vsim_reply->type != REDIS_REPLY_ARRAY) {
8707 zfree(query);
8708 if (vsim_reply) freeReplyObject(vsim_reply);
8709 continue;
8710 }
8711
8712 /* Execute ground truth query (brute force using TRUTH). */
8713 truth_reply = reconnectingRedisCommand(context,
8714 "VSIM %s FP32 %b COUNT %d TRUTH",
8715 config.vset_recall_key, query,
8716 sizeof(float)*dim, vsim_count);
8717 zfree(query);
8718
8719 if (truth_reply == NULL || truth_reply->type != REDIS_REPLY_ARRAY) {
8720 freeReplyObject(vsim_reply);
8721 if (truth_reply) freeReplyObject(truth_reply);
8722 continue;
8723 }
8724
8725 /* Build dictionary of ground truth results for fast lookup. */
8726 dictType dtype = {
8727 dictSdsHash, NULL, NULL, dictSdsKeyCompare,
8728 dictSdsDestructor, NULL, NULL
8729 };
8730 dict *truth_set = dictCreate(&dtype);
8731
8732 for (size_t i = 0; i < truth_reply->elements; i++) {
8733 sds key = sdsnew(truth_reply->element[i]->str);
8734 dictAdd(truth_set, key, NULL);
8735 }
8736
8737 /* Count overlap between HNSW results and ground truth. */
8738 int overlap = 0;
8739 for (size_t i = 0; i < vsim_reply->elements; i++) {
8740 sds vsim_key = sdsnew(vsim_reply->element[i]->str);
8741 if (dictFind(truth_set, vsim_key) != NULL) {
8742 overlap++;
8743 }
8744 sdsfree(vsim_key);
8745 }
8746
8747 dictRelease(truth_set);
8748 freeReplyObject(vsim_reply);
8749 freeReplyObject(truth_reply);
8750
8751 /* Calculate recall percentage (overlap / expected * 100). */
8752 double recall = (double)overlap / vsim_count * 100.0;
8753
8754 /* Cap at 100% against rounding errors. */
8755 if (recall > 100.0) recall = 100.0;
8756
8757 queries++;
8758 total_overlap += overlap;
8759
8760 /* Store in histogram: convert to integer by multiplying by 100,
8761 * then add 1 to shift into valid range [1, 10001] */
8762 int64_t recall_value = (int64_t)(recall * 100.0) + 1;
8763 hdr_record_value(recall_histogram, recall_value);
8764
8765 /* Display progresses. */
8766 if (mstime() > refresh_time + REFRESH_INTERVAL || !IS_TTY_OR_FAKETTY())
8767 {
8768 refresh_time = mstime();
8769 double avg_recall = (double)total_overlap / (queries * vsim_count)
8770 * 100.0;
8771
8772 if (IS_TTY_OR_FAKETTY()) printf("\x1b[0G\x1b[2K");
8773 printf("Queries: %llu | Avg recall: %.2f%%", queries, avg_recall);
8774 if (!IS_TTY_OR_FAKETTY()) printf("\n");
8775 fflush(stdout);
8776 }
8777 if (config.interval) usleep(config.interval);
8778 }
8779
8780 /* Final statistics. */
8781 printf("\n\n");
8782 printf("====================================\n");
8783 printf(" Recall Test Results\n");
8784 printf("====================================\n\n");
8785 printf("Total queries: %llu\n", queries);
8786 printf("Average recall: %.2f%%\n",
8787 (double)total_overlap / (queries * vsim_count) * 100.0);
8788
8789 /* Convert histogram statistics back to percentages. */
8790 printf("Mean recall: %.2f%%\n", (hdr_mean(recall_histogram)-1)/100.0);
8791 printf("Median recall: %.2f%%\n",
8792 (hdr_value_at_percentile(recall_histogram, 50.0)-1)/100.0);
8793 printf("StdDev: %.2f%%\n", hdr_stddev(recall_histogram)/100.0);
8794 printf("Min recall: %.2f%%\n", (hdr_min(recall_histogram)-1)/100.0);
8795 printf("Max recall: %.2f%%\n", (hdr_max(recall_histogram)-1)/100.0);
8796
8797 /* Display recall threshold distribution. */
8798 printf("\n--- Recall Thresholds ---\n");
8799 printf("At least %% of queries\n");
8800 printf("-------- ------------\n");
8801
8802 double recall_thresholds[] = {0, 50, 60, 70, 80, 85, 90, 95, 99, 100};
8803 int num_thresholds = sizeof(recall_thresholds) / sizeof(recall_thresholds[0]);
8804 for (int i = 0; i < num_thresholds; i++) {
8805 double target_recall = recall_thresholds[i];
8806 /* Convert target recall to histogram value. */
8807 int64_t target_value = (int64_t)(target_recall * 100.0) + 1;
8808
8809 /* Find what percentile this value is at. */
8810 double percentile = 0.0;
8811 for (double p = 0.0; p <= 100.0; p += 0.1) {
8812 int64_t value_at_p = hdr_value_at_percentile(recall_histogram, p);
8813 if (value_at_p >= target_value) {
8814 percentile = p;
8815 break;
8816 }
8817 }
8818
8819 /* Percentage achieving AT LEAST this recall is (100 - percentile) */
8820 double pct_achieving = 100.0 - percentile;
8821 printf("%6.1f%% %10.2f%%\n", target_recall, pct_achieving);
8822 }
8823
8824 hdr_close(recall_histogram);
8825 exit(0);
8826}
8827
8828/*------------------------------------------------------------------------------
8829 * Slave mode
8830 *--------------------------------------------------------------------------- */
8831
8832#define RDB_EOF_MARK_SIZE 40
8833
8834int sendReplconf(const char* arg1, const char* arg2) {
8835 int res = 1;
8836 fprintf(stderr, "sending REPLCONF %s %s\n", arg1, arg2);
8837 redisReply *reply = redisCommand(context, "REPLCONF %s %s", arg1, arg2);
8838
8839 /* Handle any error conditions */
8840 if(reply == NULL) {
8841 fprintf(stderr, "\nI/O error\n");
8842 exit(1);
8843 } else if(reply->type == REDIS_REPLY_ERROR) {
8844 /* non fatal, old versions may not support it */
8845 fprintf(stderr, "REPLCONF %s error: %s\n", arg1, reply->str);
8846 res = 0;
8847 }
8848 freeReplyObject(reply);
8849 return res;
8850}
8851
8852void sendCapa(void) {
8853 sendReplconf("capa", "eof");
8854}
8855
8856void sendRdbOnly(void) {
8857 sendReplconf("rdb-only", "1");
8858}
8859
8860/* Read raw bytes through a redisContext. The read operation is not greedy
8861 * and may not fill the buffer entirely.
8862 */
8863static ssize_t readConn(redisContext *c, char *buf, size_t len)
8864{
8865 return c->funcs->read(c, buf, len);
8866}
8867
8868/* Sends SYNC and reads the number of bytes in the payload. Used both by
8869 * slaveMode() and getRDB().
8870 *
8871 * send_sync if 1 means we will explicitly send SYNC command. If 0 means
8872 * we will not send SYNC command, will send the command that in c->obuf.
8873 *
8874 * Returns the size of the RDB payload to read, or 0 in case an EOF marker is used and the size
8875 * is unknown, also returns 0 in case a PSYNC +CONTINUE was found (no RDB payload).
8876 *
8877 * The out_full_mode parameter if 1 means this is a full sync, if 0 means this is partial mode. */
8878unsigned long long sendSync(redisContext *c, int send_sync, char *out_eof, int *out_full_mode) {
8879 /* To start we need to send the SYNC command and return the payload.
8880 * The hiredis client lib does not understand this part of the protocol
8881 * and we don't want to mess with its buffers, so everything is performed
8882 * using direct low-level I/O. */
8883 char buf[4096], *p;
8884 ssize_t nread;
8885
8886 if (out_full_mode) *out_full_mode = 1;
8887
8888 if (send_sync) {
8889 /* Send the SYNC command. */
8890 if (cliWriteConn(c, "SYNC\r\n", 6) != 6) {
8891 fprintf(stderr,"Error writing to master\n");
8892 exit(1);
8893 }
8894 } else {
8895 /* We have written the command into c->obuf before. */
8896 if (cliWriteConn(c, "", 0) != 0) {
8897 fprintf(stderr,"Error writing to master\n");
8898 exit(1);
8899 }
8900 }
8901
8902 /* Read $<payload>\r\n, making sure to read just up to "\n" */
8903 p = buf;
8904 while(1) {
8905 nread = readConn(c,p,1);
8906 if (nread <= 0) {
8907 fprintf(stderr,"Error reading bulk length while SYNCing\n");
8908 exit(1);
8909 }
8910 if (*p == '\n' && p != buf) break;
8911 if (*p != '\n') p++;
8912 if (p >= buf + sizeof(buf) - 1) break; /* Go back one more char for null-term. */
8913 }
8914 *p = '\0';
8915 if (buf[0] == '-') {
8916 fprintf(stderr, "SYNC with master failed: %s\n", buf);
8917 exit(1);
8918 }
8919
8920 /* Handling PSYNC responses.
8921 * Read +FULLRESYNC <replid> <offset>\r\n, after that is the $<payload> or the $EOF:<40 bytes delimiter>
8922 * Read +CONTINUE <replid>\r\n or +CONTINUE\r\n, after that is the command stream */
8923 if (!strncmp(buf, "+FULLRESYNC", 11) ||
8924 !strncmp(buf, "+CONTINUE", 9))
8925 {
8926 int sync_partial = !strncmp(buf, "+CONTINUE", 9);
8927 fprintf(stderr, "PSYNC replied %s\n", buf);
8928 p = buf;
8929 while(1) {
8930 nread = readConn(c,p,1);
8931 if (nread <= 0) {
8932 fprintf(stderr,"Error reading bulk length while PSYNCing\n");
8933 exit(1);
8934 }
8935 if (*p == '\n' && p != buf) break;
8936 if (*p != '\n') p++;
8937 if (p >= buf + sizeof(buf) - 1) break; /* Go back one more char for null-term. */
8938 }
8939 *p = '\0';
8940
8941 if (sync_partial) {
8942 if (out_full_mode) *out_full_mode = 0;
8943 return 0;
8944 }
8945 }
8946
8947 if (strncmp(buf+1,"EOF:",4) == 0 && strlen(buf+5) >= RDB_EOF_MARK_SIZE) {
8948 memcpy(out_eof, buf+5, RDB_EOF_MARK_SIZE);
8949 return 0;
8950 }
8951 return strtoull(buf+1,NULL,10);
8952}
8953
8954static void slaveMode(int send_sync) {
8955 static char eofmark[RDB_EOF_MARK_SIZE];
8956 static char lastbytes[RDB_EOF_MARK_SIZE];
8957 static int usemark = 0;
8958 static int out_full_mode;
8959 unsigned long long payload = sendSync(context, send_sync, eofmark, &out_full_mode);
8960 char buf[1024];
8961 int original_output = config.output;
8962 char *info = out_full_mode ? "Full resync" : "Partial resync";
8963
8964 if (out_full_mode == 1 && payload == 0) {
8965 /* SYNC with EOF marker or PSYNC +FULLRESYNC with EOF marker. */
8966 payload = ULLONG_MAX;
8967 memset(lastbytes,0,RDB_EOF_MARK_SIZE);
8968 usemark = 1;
8969 fprintf(stderr, "%s with master, discarding "
8970 "bytes of bulk transfer until EOF marker...\n", info);
8971 } else if (out_full_mode == 1 && payload != 0) {
8972 /* SYNC without EOF marker or PSYNC +FULLRESYNC. */
8973 fprintf(stderr, "%s with master, discarding %llu "
8974 "bytes of bulk transfer...\n", info, payload);
8975 } else if (out_full_mode == 0 && payload == 0) {
8976 /* PSYNC +CONTINUE (no RDB payload). */
8977 fprintf(stderr, "%s with master...\n", info);
8978 }
8979
8980 /* Discard the payload. */
8981 while(payload) {
8982 ssize_t nread;
8983
8984 nread = readConn(context,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
8985 if (nread <= 0) {
8986 fprintf(stderr,"Error reading RDB payload while %sing\n", info);
8987 exit(1);
8988 }
8989 payload -= nread;
8990
8991 if (usemark) {
8992 /* Update the last bytes array, and check if it matches our delimiter.*/
8993 if (nread >= RDB_EOF_MARK_SIZE) {
8994 memcpy(lastbytes,buf+nread-RDB_EOF_MARK_SIZE,RDB_EOF_MARK_SIZE);
8995 } else {
8996 int rem = RDB_EOF_MARK_SIZE-nread;
8997 memmove(lastbytes,lastbytes+nread,rem);
8998 memcpy(lastbytes+rem,buf,nread);
8999 }
9000 if (memcmp(lastbytes,eofmark,RDB_EOF_MARK_SIZE) == 0)
9001 break;
9002 }
9003 }
9004
9005 if (usemark) {
9006 unsigned long long offset = ULLONG_MAX - payload;
9007 fprintf(stderr,"%s done after %llu bytes. Logging commands from master.\n", info, offset);
9008 /* put the slave online */
9009 sleep(1);
9010 sendReplconf("ACK", "0");
9011 } else
9012 fprintf(stderr,"%s done. Logging commands from master.\n", info);
9013
9014 /* Now we can use hiredis to read the incoming protocol. */
9015 config.output = OUTPUT_CSV;
9016 while (cliReadReply(0) == REDIS_OK);
9017 config.output = original_output;
9018}
9019
9020/*------------------------------------------------------------------------------
9021 * RDB transfer mode
9022 *--------------------------------------------------------------------------- */
9023
9024/* This function implements --rdb, so it uses the replication protocol in order
9025 * to fetch the RDB file from a remote server. */
9026static void getRDB(clusterManagerNode *node) {
9027 int fd;
9028 redisContext *s;
9029 char *filename;
9030 if (node != NULL) {
9031 assert(node->context);
9032 s = node->context;
9033 filename = clusterManagerGetNodeRDBFilename(node);
9034 } else {
9035 s = context;
9036 filename = config.rdb_filename;
9037 }
9038 static char eofmark[RDB_EOF_MARK_SIZE];
9039 static char lastbytes[RDB_EOF_MARK_SIZE];
9040 static int usemark = 0;
9041 unsigned long long payload = sendSync(s, 1, eofmark, NULL);
9042 char buf[4096];
9043
9044 if (payload == 0) {
9045 payload = ULLONG_MAX;
9046 memset(lastbytes,0,RDB_EOF_MARK_SIZE);
9047 usemark = 1;
9048 fprintf(stderr,"SYNC sent to master, writing bytes of bulk transfer "
9049 "until EOF marker to '%s'\n", filename);
9050 } else {
9051 fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n",
9052 payload, filename);
9053 }
9054
9055 int write_to_stdout = !strcmp(filename,"-");
9056 /* Write to file. */
9057 if (write_to_stdout) {
9058 fd = STDOUT_FILENO;
9059 } else {
9060 fd = open(filename, O_CREAT|O_WRONLY, 0644);
9061 if (fd == -1) {
9062 fprintf(stderr, "Error opening '%s': %s\n", filename,
9063 strerror(errno));
9064 exit(1);
9065 }
9066 }
9067
9068 while(payload) {
9069 ssize_t nread, nwritten;
9070
9071 nread = readConn(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
9072 if (nread <= 0) {
9073 fprintf(stderr,"I/O Error reading RDB payload from socket\n");
9074 exit(1);
9075 }
9076 nwritten = write(fd, buf, nread);
9077 if (nwritten != nread) {
9078 fprintf(stderr,"Error writing data to file: %s\n",
9079 (nwritten == -1) ? strerror(errno) : "short write");
9080 exit(1);
9081 }
9082 payload -= nread;
9083
9084 if (usemark) {
9085 /* Update the last bytes array, and check if it matches our delimiter.*/
9086 if (nread >= RDB_EOF_MARK_SIZE) {
9087 memcpy(lastbytes,buf+nread-RDB_EOF_MARK_SIZE,RDB_EOF_MARK_SIZE);
9088 } else {
9089 int rem = RDB_EOF_MARK_SIZE-nread;
9090 memmove(lastbytes,lastbytes+nread,rem);
9091 memcpy(lastbytes+rem,buf,nread);
9092 }
9093 if (memcmp(lastbytes,eofmark,RDB_EOF_MARK_SIZE) == 0)
9094 break;
9095 }
9096 }
9097 if (usemark) {
9098 payload = ULLONG_MAX - payload - RDB_EOF_MARK_SIZE;
9099 if (!write_to_stdout && ftruncate(fd, payload) == -1)
9100 fprintf(stderr,"ftruncate failed: %s.\n", strerror(errno));
9101 fprintf(stderr,"Transfer finished with success after %llu bytes\n", payload);
9102 } else {
9103 fprintf(stderr,"Transfer finished with success.\n");
9104 }
9105 redisFree(s); /* Close the connection ASAP as fsync() may take time. */
9106 if (node)
9107 node->context = NULL;
9108 if (!write_to_stdout && fsync(fd) == -1) {
9109 fprintf(stderr,"Fail to fsync '%s': %s\n", filename, strerror(errno));
9110 exit(1);
9111 }
9112 close(fd);
9113 if (node) {
9114 sdsfree(filename);
9115 return;
9116 }
9117 exit(0);
9118}
9119
9120/*------------------------------------------------------------------------------
9121 * Bulk import (pipe) mode
9122 *--------------------------------------------------------------------------- */
9123
9124#define PIPEMODE_WRITE_LOOP_MAX_BYTES (128*1024)
9125static void pipeMode(void) {
9126 long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
9127 char obuf[1024*16]; /* Output buffer */
9128 char aneterr[ANET_ERR_LEN];
9129 redisReply *reply;
9130 int eof = 0; /* True once we consumed all the standard input. */
9131 int done = 0;
9132 char magic[20]; /* Special reply we recognize. */
9133 time_t last_read_time = time(NULL);
9134
9135 srand(time(NULL));
9136
9137 /* Use non blocking I/O. */
9138 if (anetNonBlock(aneterr,context->fd) == ANET_ERR) {
9139 fprintf(stderr, "Can't set the socket in non blocking mode: %s\n",
9140 aneterr);
9141 exit(1);
9142 }
9143
9144 context->flags &= ~REDIS_BLOCK;
9145
9146 /* Transfer raw protocol and read replies from the server at the same
9147 * time. */
9148 while(!done) {
9149 int mask = AE_READABLE;
9150
9151 if (!eof || obuf_len != 0) mask |= AE_WRITABLE;
9152 mask = aeWait(context->fd,mask,1000);
9153
9154 /* Handle the readable state: we can read replies from the server. */
9155 if (mask & AE_READABLE) {
9156 int read_error = 0;
9157
9158 do {
9159 if (!read_error && redisBufferRead(context) == REDIS_ERR) {
9160 read_error = 1;
9161 }
9162
9163 reply = NULL;
9164 if (redisGetReply(context, (void **) &reply) == REDIS_ERR) {
9165 fprintf(stderr, "Error reading replies from server\n");
9166 exit(1);
9167 }
9168 if (reply) {
9169 last_read_time = time(NULL);
9170 if (reply->type == REDIS_REPLY_ERROR) {
9171 fprintf(stderr,"%s\n", reply->str);
9172 errors++;
9173 } else if (eof && reply->type == REDIS_REPLY_STRING &&
9174 reply->len == 20) {
9175 /* Check if this is the reply to our final ECHO
9176 * command. If so everything was received
9177 * from the server. */
9178 if (memcmp(reply->str,magic,20) == 0) {
9179 printf("Last reply received from server.\n");
9180 done = 1;
9181 replies--;
9182 }
9183 }
9184 replies++;
9185 freeReplyObject(reply);
9186 }
9187 } while(reply);
9188
9189 /* Abort on read errors. We abort here because it is important
9190 * to consume replies even after a read error: this way we can
9191 * show a potential problem to the user. */
9192 if (read_error) exit(1);
9193 }
9194
9195 /* Handle the writable state: we can send protocol to the server. */
9196 if (mask & AE_WRITABLE) {
9197 ssize_t loop_nwritten = 0;
9198
9199 while(1) {
9200 /* Transfer current buffer to server. */
9201 if (obuf_len != 0) {
9202 ssize_t nwritten = cliWriteConn(context,obuf+obuf_pos,obuf_len);
9203
9204 if (nwritten == -1) {
9205 if (errno != EAGAIN && errno != EINTR) {
9206 fprintf(stderr, "Error writing to the server: %s\n",
9207 strerror(errno));
9208 exit(1);
9209 } else {
9210 nwritten = 0;
9211 }
9212 }
9213 obuf_len -= nwritten;
9214 obuf_pos += nwritten;
9215 loop_nwritten += nwritten;
9216 if (obuf_len != 0) break; /* Can't accept more data. */
9217 }
9218 if (context->err) {
9219 fprintf(stderr, "Server I/O Error: %s\n", context->errstr);
9220 exit(1);
9221 }
9222 /* If buffer is empty, load from stdin. */
9223 if (obuf_len == 0 && !eof) {
9224 ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf));
9225
9226 if (nread == 0) {
9227 /* The ECHO sequence starts with a "\r\n" so that if there
9228 * is garbage in the protocol we read from stdin, the ECHO
9229 * will likely still be properly formatted.
9230 * CRLF is ignored by Redis, so it has no effects. */
9231 char echo[] =
9232 "\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
9233 int j;
9234
9235 eof = 1;
9236 /* Everything transferred, so we queue a special
9237 * ECHO command that we can match in the replies
9238 * to make sure everything was read from the server. */
9239 for (j = 0; j < 20; j++)
9240 magic[j] = rand() & 0xff;
9241 memcpy(echo+21,magic,20);
9242 memcpy(obuf,echo,sizeof(echo)-1);
9243 obuf_len = sizeof(echo)-1;
9244 obuf_pos = 0;
9245 printf("All data transferred. Waiting for the last reply...\n");
9246 } else if (nread == -1) {
9247 fprintf(stderr, "Error reading from stdin: %s\n",
9248 strerror(errno));
9249 exit(1);
9250 } else {
9251 obuf_len = nread;
9252 obuf_pos = 0;
9253 }
9254 }
9255 if ((obuf_len == 0 && eof) ||
9256 loop_nwritten > PIPEMODE_WRITE_LOOP_MAX_BYTES) break;
9257 }
9258 }
9259
9260 /* Handle timeout, that is, we reached EOF, and we are not getting
9261 * replies from the server for a few seconds, nor the final ECHO is
9262 * received. */
9263 if (eof && config.pipe_timeout > 0 &&
9264 time(NULL)-last_read_time > config.pipe_timeout)
9265 {
9266 fprintf(stderr,"No replies for %d seconds: exiting.\n",
9267 config.pipe_timeout);
9268 errors++;
9269 break;
9270 }
9271 }
9272 printf("errors: %lld, replies: %lld\n", errors, replies);
9273 if (errors)
9274 exit(1);
9275 else
9276 exit(0);
9277}
9278
9279/*------------------------------------------------------------------------------
9280 * Find big keys
9281 *--------------------------------------------------------------------------- */
9282
9283static redisReply *sendScan(unsigned long long *it) {
9284 redisReply *reply;
9285
9286 if (config.pattern)
9287 reply = redisCommand(context, "SCAN %llu MATCH %b COUNT %d",
9288 *it, config.pattern, sdslen(config.pattern), config.count);
9289 else
9290 reply = redisCommand(context, "SCAN %llu COUNT %d",
9291 *it, config.count);
9292
9293 /* Handle any error conditions */
9294 if(reply == NULL) {
9295 fprintf(stderr, "\nI/O error\n");
9296 exit(1);
9297 } else if(reply->type == REDIS_REPLY_ERROR) {
9298 fprintf(stderr, "SCAN error: %s\n", reply->str);
9299 exit(1);
9300 } else if(reply->type != REDIS_REPLY_ARRAY) {
9301 fprintf(stderr, "Non ARRAY response from SCAN!\n");
9302 exit(1);
9303 } else if(reply->elements != 2) {
9304 fprintf(stderr, "Invalid element count from SCAN!\n");
9305 exit(1);
9306 }
9307
9308 /* Validate our types are correct */
9309 assert(reply->element[0]->type == REDIS_REPLY_STRING);
9310 assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
9311
9312 /* Update iterator */
9313 *it = strtoull(reply->element[0]->str, NULL, 10);
9314
9315 return reply;
9316}
9317
9318static int getDbSize(void) {
9319 redisReply *reply;
9320 int size;
9321
9322 reply = redisCommand(context, "DBSIZE");
9323
9324 if (reply == NULL) {
9325 fprintf(stderr, "\nI/O error\n");
9326 exit(1);
9327 } else if (reply->type == REDIS_REPLY_ERROR) {
9328 fprintf(stderr, "Couldn't determine DBSIZE: %s\n", reply->str);
9329 exit(1);
9330 } else if (reply->type != REDIS_REPLY_INTEGER) {
9331 fprintf(stderr, "Non INTEGER response from DBSIZE!\n");
9332 exit(1);
9333 }
9334
9335 /* Grab the number of keys and free our reply */
9336 size = reply->integer;
9337 freeReplyObject(reply);
9338
9339 return size;
9340}
9341
9342static int getDatabases(void) {
9343 redisReply *reply;
9344 int dbnum;
9345
9346 reply = redisCommand(context, "CONFIG GET databases");
9347
9348 if (reply == NULL) {
9349 fprintf(stderr, "\nI/O error\n");
9350 exit(1);
9351 } else if (reply->type == REDIS_REPLY_ERROR) {
9352 dbnum = 16;
9353 fprintf(stderr, "CONFIG GET databases fails: %s, use default value 16 instead\n", reply->str);
9354 } else {
9355 assert(reply->type == (config.current_resp3 ? REDIS_REPLY_MAP : REDIS_REPLY_ARRAY));
9356 assert(reply->elements == 2);
9357 dbnum = atoi(reply->element[1]->str);
9358 }
9359
9360 freeReplyObject(reply);
9361 return dbnum;
9362}
9363
9364typedef struct {
9365 char *name;
9366 char *sizecmd;
9367 char *sizeunit;
9368 unsigned long long biggest;
9369 unsigned long long count;
9370 unsigned long long totalsize;
9371 sds biggest_key;
9372} typeinfo;
9373
9374typeinfo type_string = { "string", "STRLEN", "bytes" };
9375typeinfo type_list = { "list", "LLEN", "items" };
9376typeinfo type_set = { "set", "SCARD", "members" };
9377typeinfo type_hash = { "hash", "HLEN", "fields" };
9378typeinfo type_zset = { "zset", "ZCARD", "members" };
9379typeinfo type_stream = { "stream", "XLEN", "entries" };
9380typeinfo type_other = { "other", NULL, "?" };
9381
9382static typeinfo* typeinfo_add(dict *types, char* name, typeinfo* type_template) {
9383 typeinfo *info = zmalloc(sizeof(typeinfo));
9384 *info = *type_template;
9385 info->name = sdsnew(name);
9386 dictAdd(types, info->name, info);
9387 return info;
9388}
9389
9390void type_free(dict *d, void* val) {
9391 typeinfo *info = val;
9392 UNUSED(d);
9393 if (info->biggest_key)
9394 sdsfree(info->biggest_key);
9395 sdsfree(info->name);
9396 zfree(info);
9397}
9398
9399static dictType typeinfoDictType = {
9400 dictSdsHash, /* hash function */
9401 NULL, /* key dup */
9402 NULL, /* val dup */
9403 dictSdsKeyCompare, /* key compare */
9404 NULL, /* key destructor (owned by the value)*/
9405 type_free, /* val destructor */
9406 NULL /* allow to expand */
9407};
9408
9409static void getKeyTypes(dict *types_dict, redisReply *keys, typeinfo **types) {
9410 redisReply *reply;
9411 unsigned int i;
9412
9413 /* Pipeline TYPE commands */
9414 for(i=0;i<keys->elements;i++) {
9415 const char* argv[] = {"TYPE", keys->element[i]->str};
9416 size_t lens[] = {4, keys->element[i]->len};
9417 redisAppendCommandArgv(context, 2, argv, lens);
9418 }
9419
9420 /* Retrieve types */
9421 for(i=0;i<keys->elements;i++) {
9422 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
9423 fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n",
9424 keys->element[i]->str, context->err, context->errstr);
9425 exit(1);
9426 } else if(reply->type != REDIS_REPLY_STATUS) {
9427 if(reply->type == REDIS_REPLY_ERROR) {
9428 fprintf(stderr, "TYPE returned an error: %s\n", reply->str);
9429 } else {
9430 fprintf(stderr,
9431 "Invalid reply type (%d) for TYPE on key '%s'!\n",
9432 reply->type, keys->element[i]->str);
9433 }
9434 exit(1);
9435 }
9436
9437 sds typereply = sdsnew(reply->str);
9438 dictEntry *de = dictFind(types_dict, typereply);
9439 sdsfree(typereply);
9440 typeinfo *type = NULL;
9441 if (de)
9442 type = dictGetVal(de);
9443 else if (strcmp(reply->str, "none")) /* create new types for modules, (but not for deleted keys) */
9444 type = typeinfo_add(types_dict, reply->str, &type_other);
9445 types[i] = type;
9446 freeReplyObject(reply);
9447 }
9448}
9449
9450static void getKeySizes(redisReply *keys, typeinfo **types,
9451 unsigned long long *sizes, int memkeys,
9452 long long memkeys_samples)
9453{
9454 redisReply *reply;
9455 unsigned int i;
9456
9457 /* Pipeline size commands */
9458 for(i=0;i<keys->elements;i++) {
9459 /* Skip keys that disappeared between SCAN and TYPE (or unknown types when not in memkeys mode) */
9460 if(!types[i] || (!types[i]->sizecmd && !memkeys))
9461 continue;
9462
9463 if (!memkeys) {
9464 const char* argv[] = {types[i]->sizecmd, keys->element[i]->str};
9465 size_t lens[] = {strlen(types[i]->sizecmd), keys->element[i]->len};
9466 redisAppendCommandArgv(context, 2, argv, lens);
9467 } else if (memkeys_samples == -1) {
9468 const char* argv[] = {"MEMORY", "USAGE", keys->element[i]->str};
9469 size_t lens[] = {6, 5, keys->element[i]->len};
9470 redisAppendCommandArgv(context, 3, argv, lens);
9471 } else {
9472 sds samplesstr = sdsfromlonglong(memkeys_samples);
9473 const char* argv[] = {"MEMORY", "USAGE", keys->element[i]->str, "SAMPLES", samplesstr};
9474 size_t lens[] = {6, 5, keys->element[i]->len, 7, sdslen(samplesstr)};
9475 redisAppendCommandArgv(context, 5, argv, lens);
9476 sdsfree(samplesstr);
9477 }
9478 }
9479
9480 /* Retrieve sizes */
9481 for(i=0;i<keys->elements;i++) {
9482 /* Skip keys that disappeared between SCAN and TYPE (or unknown types when not in memkeys mode) */
9483 if(!types[i] || (!types[i]->sizecmd && !memkeys)) {
9484 sizes[i] = 0;
9485 continue;
9486 }
9487
9488 /* Retrieve size */
9489 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
9490 fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
9491 keys->element[i]->str, context->err, context->errstr);
9492 exit(1);
9493 } else if(reply->type != REDIS_REPLY_INTEGER) {
9494 /* Theoretically the key could have been removed and
9495 * added as a different type between TYPE and SIZE */
9496 fprintf(stderr,
9497 "Warning: %s on '%s' failed (may have changed type)\n",
9498 !memkeys? types[i]->sizecmd: "MEMORY USAGE",
9499 keys->element[i]->str);
9500 sizes[i] = 0;
9501 } else {
9502 sizes[i] = reply->integer;
9503 }
9504
9505 freeReplyObject(reply);
9506 }
9507}
9508
9509/* In cluster mode we may need to send the READONLY command.
9510 Ignore the error in case the server isn't using cluster mode. */
9511static void sendReadOnly(void) {
9512 redisReply *read_reply;
9513 read_reply = redisCommand(context, "READONLY");
9514 if (read_reply == NULL){
9515 fprintf(stderr, "\nI/O error\n");
9516 exit(1);
9517 } else if (read_reply->type == REDIS_REPLY_ERROR &&
9518 strcmp(read_reply->str, "ERR This instance has cluster support disabled") != 0 &&
9519 strncmp(read_reply->str, "ERR unknown command", 19) != 0) {
9520 fprintf(stderr, "Error: %s\n", read_reply->str);
9521 exit(1);
9522 }
9523 freeReplyObject(read_reply);
9524}
9525
9526static int displayKeyStatsProgressbar(unsigned long long sampled,
9527 unsigned long long total_keys);
9528
9529static void findBigKeys(int memkeys, long long memkeys_samples) {
9530 unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0, scan_loops = 0;
9531 redisReply *reply, *keys;
9532 unsigned int arrsize=0, i;
9533 dictIterator di;
9534 dictEntry *de;
9535 typeinfo **types = NULL;
9536 double pct;
9537 long long refresh_time = mstime();
9538
9539 dict *types_dict = dictCreate(&typeinfoDictType);
9540 typeinfo_add(types_dict, "string", &type_string);
9541 typeinfo_add(types_dict, "list", &type_list);
9542 typeinfo_add(types_dict, "set", &type_set);
9543 typeinfo_add(types_dict, "hash", &type_hash);
9544 typeinfo_add(types_dict, "zset", &type_zset);
9545 typeinfo_add(types_dict, "stream", &type_stream);
9546
9547 signal(SIGINT, longStatLoopModeStop);
9548 /* Total keys pre scanning */
9549 total_keys = getDbSize();
9550
9551 /* Status message */
9552 printf("\n# Scanning the entire keyspace to find biggest keys as well as\n");
9553 printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
9554 printf("# per 100 SCAN commands (not usually needed).\n\n");
9555
9556 /* Use readonly in cluster */
9557 sendReadOnly();
9558
9559 /* SCAN loop */
9560 do {
9561 /* Calculate approximate percentage completion */
9562 pct = 100 * (double)sampled/total_keys;
9563
9564 /* Grab some keys and point to the keys array */
9565 reply = sendScan(&it);
9566 scan_loops++;
9567 keys = reply->element[1];
9568
9569 /* Reallocate our type and size array if we need to */
9570 if(keys->elements > arrsize) {
9571 types = zrealloc(types, sizeof(typeinfo*)*keys->elements);
9572 sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
9573
9574 if(!types || !sizes) {
9575 fprintf(stderr, "Failed to allocate storage for keys!\n");
9576 exit(1);
9577 }
9578
9579 arrsize = keys->elements;
9580 }
9581
9582 /* Retrieve types and then sizes */
9583 getKeyTypes(types_dict, keys, types);
9584 getKeySizes(keys, types, sizes, memkeys, memkeys_samples);
9585
9586 /* Now update our stats */
9587 for(i=0;i<keys->elements;i++) {
9588 typeinfo *type = types[i];
9589 /* Skip keys that disappeared between SCAN and TYPE */
9590 if(!type)
9591 continue;
9592
9593 type->totalsize += sizes[i];
9594 type->count++;
9595 totlen += keys->element[i]->len;
9596 sampled++;
9597
9598 if(type->biggest<sizes[i]) {
9599 /* Keep track of biggest key name for this type */
9600 if (type->biggest_key)
9601 sdsfree(type->biggest_key);
9602 type->biggest_key = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
9603 if(!type->biggest_key) {
9604 fprintf(stderr, "Failed to allocate memory for key!\n");
9605 exit(1);
9606 }
9607
9608 /* We only show the original progress output when writing to a file */
9609 if (!IS_TTY_OR_FAKETTY()) {
9610 printf("[%05.2f%%] Biggest %-6s found so far %s with %llu %s\n",
9611 pct, type->name, type->biggest_key, sizes[i],
9612 !memkeys? type->sizeunit: "bytes");
9613 }
9614
9615 /* Keep track of the biggest size for this type */
9616 type->biggest = sizes[i];
9617 }
9618
9619 /* Update overall progress
9620 * We only show the original progress output when writing to a file */
9621 if (sampled % 1000000 == 0 && !IS_TTY_OR_FAKETTY()) {
9622 printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
9623 }
9624
9625 /* Show the progress bar in TTY */
9626 if (mstime() > refresh_time + REFRESH_INTERVAL && IS_TTY_OR_FAKETTY()) {
9627 int line_count = 0;
9628 refresh_time = mstime();
9629
9630 line_count = displayKeyStatsProgressbar(sampled, total_keys);
9631 line_count += cleanPrintfln("");
9632
9633 dictInitIterator(&di, types_dict);
9634 while ((de = dictNext(&di))) {
9635 typeinfo *current_type = dictGetVal(de);
9636 if (current_type->biggest > 0) {
9637 line_count += cleanPrintfln("Biggest %-9s found so far %s with %llu %s",
9638 current_type->name, current_type->biggest_key, current_type->biggest,
9639 !memkeys? current_type->sizeunit: "bytes");
9640 }
9641 }
9642 dictResetIterator(&di);
9643
9644 printf("\033[%dA\r", line_count);
9645 }
9646 }
9647
9648 /* Sleep if we've been directed to do so */
9649 if (config.interval && (scan_loops % 100) == 0) {
9650 usleep(config.interval);
9651 }
9652
9653 freeReplyObject(reply);
9654 } while(force_cancel_loop == 0 && it != 0);
9655
9656 /* Final progress bar if TTY */
9657 if (IS_TTY_OR_FAKETTY()) {
9658 displayKeyStatsProgressbar(sampled, total_keys);
9659
9660 /* Clean the types info shown during the progress bar */
9661 int line_count = 0;
9662 dictInitIterator(&di, types_dict);
9663 while ((de = dictNext(&di)))
9664 line_count += cleanPrintfln("");
9665 dictResetIterator(&di);
9666 printf("\033[%dA\r", line_count);
9667 }
9668
9669 if(types) zfree(types);
9670 if(sizes) zfree(sizes);
9671
9672 /* We're done */
9673 printf("\n-------- summary -------\n\n");
9674
9675 /* Show percentage and sampled output when writing to a file */
9676 if (!IS_TTY_OR_FAKETTY()) {
9677 if (force_cancel_loop) printf("[%05.2f%%] ", pct);
9678 printf("Sampled %llu keys in the keyspace!\n", sampled);
9679 }
9680
9681 printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
9682 totlen, totlen ? (double)totlen/sampled : 0);
9683
9684 /* Output the biggest keys we found, for types we did find */
9685 dictInitIterator(&di, types_dict);
9686 while ((de = dictNext(&di))) {
9687 typeinfo *type = dictGetVal(de);
9688 if(type->biggest_key) {
9689 printf("Biggest %6s found %s has %llu %s\n", type->name, type->biggest_key,
9690 type->biggest, !memkeys? type->sizeunit: "bytes");
9691 }
9692 }
9693 dictResetIterator(&di);
9694
9695 printf("\n");
9696
9697 dictInitIterator(&di, types_dict);
9698 while ((de = dictNext(&di))) {
9699 typeinfo *type = dictGetVal(de);
9700 printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
9701 type->count, type->name, type->totalsize, !memkeys? type->sizeunit: "bytes",
9702 sampled ? 100 * (double)type->count/sampled : 0,
9703 type->count ? (double)type->totalsize/type->count : 0);
9704 }
9705 dictResetIterator(&di);
9706
9707 dictRelease(types_dict);
9708
9709 /* Success! */
9710 exit(0);
9711}
9712
9713static void getKeyFreqs(redisReply *keys, unsigned long long *freqs) {
9714 redisReply *reply;
9715 unsigned int i;
9716
9717 /* Pipeline OBJECT freq commands */
9718 for(i=0;i<keys->elements;i++) {
9719 const char* argv[] = {"OBJECT", "FREQ", keys->element[i]->str};
9720 size_t lens[] = {6, 4, keys->element[i]->len};
9721 redisAppendCommandArgv(context, 3, argv, lens);
9722 }
9723
9724 /* Retrieve freqs */
9725 for(i=0;i<keys->elements;i++) {
9726 if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
9727 sds keyname = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
9728 fprintf(stderr, "Error getting freq for key '%s' (%d: %s)\n",
9729 keyname, context->err, context->errstr);
9730 sdsfree(keyname);
9731 exit(1);
9732 } else if(reply->type != REDIS_REPLY_INTEGER) {
9733 if(reply->type == REDIS_REPLY_ERROR) {
9734 fprintf(stderr, "Error: %s\n", reply->str);
9735 exit(1);
9736 } else {
9737 sds keyname = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
9738 fprintf(stderr, "Warning: OBJECT freq on '%s' failed (may have been deleted)\n", keyname);
9739 sdsfree(keyname);
9740 freqs[i] = 0;
9741 }
9742 } else {
9743 freqs[i] = reply->integer;
9744 }
9745 freeReplyObject(reply);
9746 }
9747}
9748
9749#define HOTKEYS_SAMPLE 16
9750static void findHotKeys(void) {
9751 redisReply *keys, *reply;
9752 unsigned long long counters[HOTKEYS_SAMPLE] = {0};
9753 sds hotkeys[HOTKEYS_SAMPLE] = {NULL};
9754 unsigned long long sampled = 0, total_keys, *freqs = NULL, it = 0, scan_loops = 0;
9755 unsigned int arrsize = 0, i;
9756 int k;
9757 double pct;
9758 long long refresh_time = mstime();
9759
9760 signal(SIGINT, longStatLoopModeStop);
9761 /* Total keys pre scanning */
9762 total_keys = getDbSize();
9763
9764 /* Status message */
9765 printf("\n# Scanning the entire keyspace to find hot keys as well as\n");
9766 printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
9767 printf("# per 100 SCAN commands (not usually needed).\n\n");
9768
9769 /* Use readonly in cluster */
9770 sendReadOnly();
9771
9772 /* SCAN loop */
9773 do {
9774 /* Calculate approximate percentage completion */
9775 pct = 100 * (double)sampled/total_keys;
9776
9777 /* Grab some keys and point to the keys array */
9778 reply = sendScan(&it);
9779 scan_loops++;
9780 keys = reply->element[1];
9781
9782 /* Reallocate our freqs array if we need to */
9783 if(keys->elements > arrsize) {
9784 freqs = zrealloc(freqs, sizeof(unsigned long long)*keys->elements);
9785
9786 if(!freqs) {
9787 fprintf(stderr, "Failed to allocate storage for keys!\n");
9788 exit(1);
9789 }
9790
9791 arrsize = keys->elements;
9792 }
9793
9794 getKeyFreqs(keys, freqs);
9795
9796 /* Now update our stats */
9797 for(i=0;i<keys->elements;i++) {
9798 sampled++;
9799
9800 /* Update overall progress.
9801 * Only show the original progress output when writing to a file */
9802 if (sampled % 1000000 == 0 && !IS_TTY_OR_FAKETTY()) {
9803 printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
9804 }
9805
9806 /* Use eviction pool here */
9807 k = 0;
9808 while (k < HOTKEYS_SAMPLE && freqs[i] > counters[k]) k++;
9809 if (k == 0) continue;
9810 k--;
9811 if (k == 0 || counters[k] == 0) {
9812 sdsfree(hotkeys[k]);
9813 } else {
9814 sdsfree(hotkeys[0]);
9815 memmove(counters,counters+1,sizeof(counters[0])*k);
9816 memmove(hotkeys,hotkeys+1,sizeof(hotkeys[0])*k);
9817 }
9818 counters[k] = freqs[i];
9819 hotkeys[k] = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
9820
9821 /* Only show the original progress output when writing to a file */
9822 if (!IS_TTY_OR_FAKETTY()) {
9823 printf("[%05.2f%%] Hot key %s found so far with counter %llu\n",
9824 pct, hotkeys[k], freqs[i]);
9825 }
9826 }
9827
9828 /* Show the progress bar in TTY */
9829 if (mstime() > refresh_time + REFRESH_INTERVAL && IS_TTY_OR_FAKETTY()) {
9830 int line_count = 0;
9831 refresh_time = mstime();
9832
9833 line_count = displayKeyStatsProgressbar(sampled, total_keys);
9834 line_count += cleanPrintfln("");
9835
9836 for (k = HOTKEYS_SAMPLE - 1; k >= 0; k--) {
9837 if (counters[k] > 0) {
9838 line_count += cleanPrintfln("hot key found with counter: %llu\tkeyname: %s",
9839 counters[k], hotkeys[k]);
9840 }
9841 }
9842
9843 printf("\033[%dA\r", line_count);
9844 }
9845
9846 /* Sleep if we've been directed to do so */
9847 if (config.interval && (scan_loops % 100) == 0) {
9848 usleep(config.interval);
9849 }
9850
9851 freeReplyObject(reply);
9852 } while(force_cancel_loop ==0 && it != 0);
9853
9854 /* Final progress bar in TTY */
9855 if (IS_TTY_OR_FAKETTY()) {
9856 displayKeyStatsProgressbar(sampled, total_keys);
9857
9858 /* clean the types info shown during the progress bar */
9859 int line_count = 0;
9860 for (k = 0; k <= HOTKEYS_SAMPLE; k++)
9861 line_count += cleanPrintfln("");
9862 printf("\033[%dA\r", line_count);
9863 }
9864
9865 if (freqs) zfree(freqs);
9866
9867 /* We're done */
9868 printf("\n-------- summary -------\n\n");
9869
9870 /* Show the original output when writing to a file */
9871 if (!IS_TTY_OR_FAKETTY()) {
9872 if(force_cancel_loop) printf("[%05.2f%%] ",pct);
9873 printf("Sampled %llu keys in the keyspace!\n", sampled);
9874 }
9875
9876 for (k = HOTKEYS_SAMPLE - 1; k >= 0; k--) {
9877 if (counters[k] > 0) {
9878 printf("hot key found with counter: %llu\tkeyname: %s\n", counters[k], hotkeys[k]);
9879 sdsfree(hotkeys[k]);
9880 }
9881 }
9882
9883 exit(0);
9884}
9885
9886/*------------------------------------------------------------------------------
9887 * Stats mode
9888 *--------------------------------------------------------------------------- */
9889
9890/* Return the specified INFO field from the INFO command output "info".
9891 * A new buffer is allocated for the result, that needs to be free'd.
9892 * If the field is not found NULL is returned. */
9893static char *getInfoField(char *info, char *field) {
9894 char *p = strstr(info,field);
9895 char *n1, *n2;
9896 char *result;
9897
9898 if (!p) return NULL;
9899 p += strlen(field)+1;
9900 n1 = strchr(p,'\r');
9901 n2 = strchr(p,',');
9902 if (n2 && n2 < n1) n1 = n2;
9903 result = zmalloc(sizeof(char)*(n1-p)+1);
9904 memcpy(result,p,(n1-p));
9905 result[n1-p] = '\0';
9906 return result;
9907}
9908
9909/* Like the above function but automatically convert the result into
9910 * a long. On error (missing field) LONG_MIN is returned. */
9911static long getLongInfoField(char *info, char *field) {
9912 char *value = getInfoField(info,field);
9913 long l;
9914
9915 if (!value) return LONG_MIN;
9916 l = strtol(value,NULL,10);
9917 zfree(value);
9918 return l;
9919}
9920
9921/* Convert number of bytes into a human readable string of the form:
9922 * 1003B, 4.03K, 100.00M, 2.32G, 3.01T
9923 * Returns the parameter `s` containing the converted number. */
9924char *bytesToHuman(char *s, size_t size, long long n) {
9925 double d;
9926 char *r = s;
9927
9928 if (n < 0) {
9929 *s = '-';
9930 s++;
9931 n = -n;
9932 }
9933 if (n < 1024) {
9934 /* Bytes */
9935 snprintf(s,size,"%lldB",n);
9936 } else if (n < (1024*1024)) {
9937 d = (double)n/(1024);
9938 snprintf(s,size,"%.2fK",d);
9939 } else if (n < (1024LL*1024*1024)) {
9940 d = (double)n/(1024*1024);
9941 snprintf(s,size,"%.2fM",d);
9942 } else if (n < (1024LL*1024*1024*1024)) {
9943 d = (double)n/(1024LL*1024*1024);
9944 snprintf(s,size,"%.2fG",d);
9945 } else if (n < (1024LL*1024*1024*1024*1024)) {
9946 d = (double)n/(1024LL*1024*1024*1024);
9947 snprintf(s,size,"%.2fT",d);
9948 }
9949
9950 return r;
9951}
9952
9953static void statMode(void) {
9954 redisReply *reply;
9955 long aux, requests = 0;
9956 int dbnum = getDatabases();
9957 int i = 0;
9958
9959 while(1) {
9960 char buf[64];
9961 int j;
9962
9963 reply = reconnectingRedisCommand(context,"INFO");
9964 if (reply == NULL) {
9965 fprintf(stderr, "\nI/O error\n");
9966 exit(1);
9967 } else if (reply->type == REDIS_REPLY_ERROR) {
9968 fprintf(stderr, "ERROR: %s\n", reply->str);
9969 exit(1);
9970 }
9971
9972 if ((i++ % 20) == 0) {
9973 printf(
9974"------- data ------ --------------------- load -------------------- - child -\n"
9975"keys mem clients blocked requests connections \n");
9976 }
9977
9978 /* Keys */
9979 aux = 0;
9980 for (j = 0; j < dbnum; j++) {
9981 long k;
9982
9983 snprintf(buf,sizeof(buf),"db%d:keys",j);
9984 k = getLongInfoField(reply->str,buf);
9985 if (k == LONG_MIN) continue;
9986 aux += k;
9987 }
9988 snprintf(buf,sizeof(buf),"%ld",aux);
9989 printf("%-11s",buf);
9990
9991 /* Used memory */
9992 aux = getLongInfoField(reply->str,"used_memory");
9993 bytesToHuman(buf,sizeof(buf),aux);
9994 printf("%-8s",buf);
9995
9996 /* Clients */
9997 aux = getLongInfoField(reply->str,"connected_clients");
9998 snprintf(buf,sizeof(buf),"%ld",aux);
9999 printf(" %-8s",buf);
10000
10001 /* Blocked (BLPOPPING) Clients */
10002 aux = getLongInfoField(reply->str,"blocked_clients");
10003 snprintf(buf,sizeof(buf),"%ld",aux);
10004 printf("%-8s",buf);
10005
10006 /* Requests */
10007 aux = getLongInfoField(reply->str,"total_commands_processed");
10008 snprintf(buf,sizeof(buf),"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
10009 printf("%-19s",buf);
10010 requests = aux;
10011
10012 /* Connections */
10013 aux = getLongInfoField(reply->str,"total_connections_received");
10014 snprintf(buf,sizeof(buf),"%ld",aux);
10015 printf(" %-12s",buf);
10016
10017 /* Children */
10018 aux = getLongInfoField(reply->str,"bgsave_in_progress");
10019 aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1;
10020 aux |= getLongInfoField(reply->str,"loading") << 2;
10021 switch(aux) {
10022 case 0: break;
10023 case 1:
10024 printf("SAVE");
10025 break;
10026 case 2:
10027 printf("AOF");
10028 break;
10029 case 3:
10030 printf("SAVE+AOF");
10031 break;
10032 case 4:
10033 printf("LOAD");
10034 break;
10035 }
10036
10037 printf("\n");
10038 freeReplyObject(reply);
10039 usleep(config.interval);
10040 }
10041}
10042
10043/*------------------------------------------------------------------------------
10044 * Scan mode
10045 *--------------------------------------------------------------------------- */
10046
10047static void scanMode(void) {
10048 redisReply *reply;
10049 unsigned long long cur = 0;
10050 signal(SIGINT, longStatLoopModeStop);
10051 do {
10052 reply = sendScan(&cur);
10053 for (unsigned int j = 0; j < reply->element[1]->elements; j++) {
10054 if (config.output == OUTPUT_STANDARD) {
10055 sds out = sdscatrepr(sdsempty(), reply->element[1]->element[j]->str,
10056 reply->element[1]->element[j]->len);
10057 printf("%s\n", out);
10058 sdsfree(out);
10059 } else {
10060 printf("%s\n", reply->element[1]->element[j]->str);
10061 }
10062 }
10063 freeReplyObject(reply);
10064 if (config.interval) usleep(config.interval);
10065 } while(force_cancel_loop == 0 && cur != 0);
10066
10067 exit(0);
10068}
10069
10070/*------------------------------------------------------------------------------
10071 * LRU test mode
10072 *--------------------------------------------------------------------------- */
10073
10074/* Return an integer from min to max (both inclusive) using a power-law
10075 * distribution, depending on the value of alpha: the greater the alpha
10076 * the more bias towards lower values.
10077 *
10078 * With alpha = 6.2 the output follows the 80-20 rule where 20% of
10079 * the returned numbers will account for 80% of the frequency. */
10080long long powerLawRand(long long min, long long max, double alpha) {
10081 double pl, r;
10082
10083 max += 1;
10084 r = ((double)rand()) / RAND_MAX;
10085 pl = pow(
10086 ((pow(max,alpha+1) - pow(min,alpha+1))*r + pow(min,alpha+1)),
10087 (1.0/(alpha+1)));
10088 return (max-1-(long long)pl)+min;
10089}
10090
10091/* Generates a key name among a set of lru_test_sample_size keys, using
10092 * an 80-20 distribution. */
10093void LRUTestGenKey(char *buf, size_t buflen) {
10094 snprintf(buf, buflen, "lru:%lld",
10095 powerLawRand(1, config.lru_test_sample_size, 6.2));
10096}
10097
10098#define LRU_CYCLE_PERIOD 1000 /* 1000 milliseconds. */
10099#define LRU_CYCLE_PIPELINE_SIZE 250
10100static void LRUTestMode(void) {
10101 redisReply *reply;
10102 char key[128];
10103 long long start_cycle;
10104 int j;
10105
10106 srand(time(NULL)^getpid());
10107 while(1) {
10108 /* Perform cycles of 1 second with 50% writes and 50% reads.
10109 * We use pipelining batching writes / reads N times per cycle in order
10110 * to fill the target instance easily. */
10111 start_cycle = mstime();
10112 long long hits = 0, misses = 0;
10113 while(mstime() - start_cycle < LRU_CYCLE_PERIOD) {
10114 /* Write cycle. */
10115 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
10116 char val[6];
10117 val[5] = '\0';
10118 for (int i = 0; i < 5; i++) val[i] = 'A'+rand()%('z'-'A');
10119 LRUTestGenKey(key,sizeof(key));
10120 redisAppendCommand(context, "SET %s %s",key,val);
10121 }
10122 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++)
10123 redisGetReply(context, (void**)&reply);
10124
10125 /* Read cycle. */
10126 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
10127 LRUTestGenKey(key,sizeof(key));
10128 redisAppendCommand(context, "GET %s",key);
10129 }
10130 for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
10131 if (redisGetReply(context, (void**)&reply) == REDIS_OK) {
10132 switch(reply->type) {
10133 case REDIS_REPLY_ERROR:
10134 fprintf(stderr, "%s\n", reply->str);
10135 break;
10136 case REDIS_REPLY_NIL:
10137 misses++;
10138 break;
10139 default:
10140 hits++;
10141 break;
10142 }
10143 }
10144 }
10145
10146 if (context->err) {
10147 fprintf(stderr,"I/O error during LRU test\n");
10148 exit(1);
10149 }
10150 }
10151 /* Print stats. */
10152 long long total_gets = hits + misses;
10153 printf(
10154 "%lld Gets/sec | Hits: %lld (%.2f%%) | Misses: %lld (%.2f%%)\n",
10155 hits+misses,
10156 hits, total_gets > 0 ? (double)hits/total_gets*100 : 0.0,
10157 misses, total_gets > 0 ? (double)misses/total_gets*100 : 0.0);
10158 }
10159 exit(0);
10160}
10161
10162/*------------------------------------------------------------------------------
10163 * Intrinsic latency mode.
10164 *
10165 * Measure max latency of a running process that does not result from
10166 * syscalls. Basically this software should provide a hint about how much
10167 * time the kernel leaves the process without a chance to run.
10168 *--------------------------------------------------------------------------- */
10169
10170/* This is just some computation the compiler can't optimize out.
10171 * Should run in less than 100-200 microseconds even using very
10172 * slow hardware. Runs in less than 10 microseconds in modern HW. */
10173unsigned long compute_something_fast(void) {
10174 unsigned char s[256], i, j, t;
10175 int count = 1000, k;
10176 unsigned long output = 0;
10177
10178 for (k = 0; k < 256; k++) s[k] = k;
10179
10180 i = 0;
10181 j = 0;
10182 while(count--) {
10183 i++;
10184 j = j + s[i];
10185 t = s[i];
10186 s[i] = s[j];
10187 s[j] = t;
10188 output += s[(s[i]+s[j])&255];
10189 }
10190 return output;
10191}
10192
10193static void sigIntHandler(int s) {
10194 UNUSED(s);
10195
10196 if (config.monitor_mode || config.pubsub_mode) {
10197 close(context->fd);
10198 context->fd = REDIS_INVALID_FD;
10199 config.blocking_state_aborted = 1;
10200 } else {
10201 exit(1);
10202 }
10203}
10204
10205static void intrinsicLatencyMode(void) {
10206 long long test_end, run_time, max_latency = 0, runs = 0;
10207
10208 run_time = (long long)config.intrinsic_latency_duration * 1000000;
10209 test_end = ustime() + run_time;
10210 signal(SIGINT, longStatLoopModeStop);
10211
10212 while(1) {
10213 long long start, end, latency;
10214
10215 start = ustime();
10216 compute_something_fast();
10217 end = ustime();
10218 latency = end-start;
10219 runs++;
10220 if (latency <= 0) continue;
10221
10222 /* Reporting */
10223 if (latency > max_latency) {
10224 max_latency = latency;
10225 printf("Max latency so far: %lld microseconds.\n", max_latency);
10226 }
10227
10228 double avg_us = (double)run_time/runs;
10229 double avg_ns = avg_us * 1e3;
10230 if (force_cancel_loop || end > test_end) {
10231 printf("\n%lld total runs "
10232 "(avg latency: "
10233 "%.4f microseconds / %.2f nanoseconds per run).\n",
10234 runs, avg_us, avg_ns);
10235 printf("Worst run took %.0fx longer than the average latency.\n",
10236 max_latency / avg_us);
10237 exit(0);
10238 }
10239 }
10240}
10241
10242static sds askPassword(const char *msg) {
10243 linenoiseMaskModeEnable();
10244 sds auth = linenoise(msg);
10245 linenoiseMaskModeDisable();
10246 return auth;
10247}
10248
10249/* Prints out the hint completion string for a given input prefix string. */
10250void testHint(const char *input) {
10251 cliInitHelp();
10252
10253 sds hint = getHintForInput(input);
10254 printf("%s\n", hint);
10255 exit(0);
10256}
10257
10258sds readHintSuiteLine(char buf[], size_t size, FILE *fp) {
10259 while (fgets(buf, size, fp) != NULL) {
10260 if (buf[0] != '#') {
10261 sds input = sdsnew(buf);
10262
10263 /* Strip newline. */
10264 input = sdstrim(input, "\n");
10265 return input;
10266 }
10267 }
10268 return NULL;
10269}
10270
10271/* Runs a suite of hint completion tests contained in a file. */
10272void testHintSuite(char *filename) {
10273 FILE *fp;
10274 char buf[256];
10275 sds line, input, expected, hint;
10276 int pass=0, fail=0;
10277 int argc;
10278 char **argv;
10279
10280 fp = fopen(filename, "r");
10281 if (!fp) {
10282 fprintf(stderr,
10283 "Can't open file '%s': %s\n", filename, strerror(errno));
10284 exit(-1);
10285 }
10286
10287 cliInitHelp();
10288
10289 while (1) {
10290 line = readHintSuiteLine(buf, sizeof(buf), fp);
10291 if (line == NULL) break;
10292 argv = sdssplitargs(line, &argc);
10293 sdsfree(line);
10294 if (argc == 0) {
10295 sdsfreesplitres(argv, argc);
10296 continue;
10297 }
10298
10299 if (argc == 1) {
10300 fprintf(stderr,
10301 "Missing expected hint for input '%s'\n", argv[0]);
10302 exit(-1);
10303 }
10304 input = argv[0];
10305 expected = argv[1];
10306 hint = getHintForInput(input);
10307 if (config.verbose) {
10308 printf("Input: '%s', Expected: '%s', Hint: '%s'\n", input, expected, hint);
10309 }
10310
10311 /* Strip trailing spaces from hint - they don't matter. */
10312 while (hint != NULL && sdslen(hint) > 0 && hint[sdslen(hint) - 1] == ' ') {
10313 sdssetlen(hint, sdslen(hint) - 1);
10314 hint[sdslen(hint)] = '\0';
10315 }
10316
10317 if (hint == NULL || strcmp(hint, expected) != 0) {
10318 fprintf(stderr, "Test case '%s' FAILED: expected '%s', got '%s'\n", input, expected, hint);
10319 ++fail;
10320 }
10321 else {
10322 ++pass;
10323 }
10324 sdsfreesplitres(argv, argc);
10325 sdsfree(hint);
10326 }
10327 fclose(fp);
10328
10329 printf("%s: %d/%d passed\n", fail == 0 ? "SUCCESS" : "FAILURE", pass, pass + fail);
10330 exit(fail);
10331}
10332
10333/*------------------------------------------------------------------------------
10334 * Keystats
10335 *--------------------------------------------------------------------------- */
10336
10337/* Key name length distribution. */
10338
10339typedef struct size_dist_entry {
10340 unsigned long long size; /* Key name size in bytes. */
10341 unsigned long long count; /* Number of key names that are less or equal to the size. */
10342} size_dist_entry;
10343
10344typedef struct size_dist {
10345 unsigned long long total_count; /* Total number of key names in the distribution. */
10346 unsigned long long total_size; /* Sum of all the key name sizes in bytes. */
10347 unsigned long long max_size; /* Highest key name size in bytes. */
10348 size_dist_entry *size_dist; /* Array of sizes and key names count per size. */
10349} size_dist;
10350
10351/* distribution is an array initialized with last element {0, 0}
10352 * for instance: size_dist_entry distribution[] = { {32, 0}, {256, 0}, {0, 0} }; */
10353static void sizeDistInit(size_dist *dist, size_dist_entry *distribution) {
10354 dist->max_size = 0;
10355 dist->total_count = 0;
10356 dist->total_size = 0;
10357 dist->size_dist = distribution;
10358}
10359
10360static void addSizeDist(size_dist *dist, unsigned long long size) {
10361 dist->total_count++;
10362 dist->total_size += size;
10363
10364 if (size > dist->max_size)
10365 dist->max_size = size;
10366
10367 int j;
10368 for (j=0; dist->size_dist[j].size && size > dist->size_dist[j].size; j++);
10369 dist->size_dist[j].count++;
10370}
10371
10372static int displayKeyStatsLengthDist(size_dist *dist) {
10373 int line_count = 0;
10374 unsigned long long total_keys = 0, size;
10375 char buf[2][256];
10376
10377 line_count += cleanPrintfln("Key name length Percentile Total keys");
10378 line_count += cleanPrintfln("--------------- ---------- -----------");
10379
10380 for (int i=0; dist->size_dist[i].size; i++) {
10381 if (dist->size_dist[i].count) {
10382 if (dist->max_size < dist->size_dist[i].size) {
10383 size = dist->max_size;
10384 } else {
10385 size = dist->size_dist[i].size;
10386 }
10387 total_keys += dist->size_dist[i].count;
10388 line_count += cleanPrintfln("%15s %9.4f%% %11llu",
10389 bytesToHuman(buf[1], sizeof(buf[1]), size),
10390 (double)100 * total_keys / dist->total_count,
10391 total_keys);
10392 }
10393 }
10394
10395 if (total_keys < dist->total_count) {
10396 line_count += cleanPrintfln(" inf %9.4f%% %11llu", 100.0, dist->total_count);
10397 }
10398
10399 line_count += cleanPrintfln("Total key length is %s (%s avg)",
10400 bytesToHuman(buf[0], sizeof(buf[0]), dist->total_size),
10401 dist->total_count ? bytesToHuman(buf[1], sizeof(buf[1]), dist->total_size/dist->total_count) : "0");
10402
10403 return line_count;
10404}
10405
10406#define PROGRESSBAR_WIDTH 60
10407static int displayKeyStatsProgressbar(unsigned long long sampled,
10408 unsigned long long total_keys)
10409{
10410 int line_count = 0;
10411 char progressbar[512];
10412 char buf[2][128];
10413
10414 /* We can go over 100% if keys are added in the middle of the scans.
10415 * Cap at 100% or the progressbar memset will overflow. */
10416 double completion_pct = total_keys ? sampled < total_keys ? (double) sampled/total_keys : 1 : 0;
10417
10418 /* If we are not redirecting to a file, build the progress bar */
10419 if (IS_TTY_OR_FAKETTY()) {
10420 int completed_width = (int)round(PROGRESSBAR_WIDTH * completion_pct);
10421 memset(buf[0], '|', completed_width);
10422 buf[0][completed_width]= '\0';
10423
10424 int uncompleted_width = PROGRESSBAR_WIDTH - completed_width;
10425 memset(buf[1], '-', uncompleted_width);
10426 buf[1][uncompleted_width]= '\0';
10427
10428 char red[] = "\033[31m";
10429 char green[] = "\033[32m";
10430 char default_color[] = "\033[39m";
10431 snprintf(progressbar, sizeof(progressbar), "%s%s%s%s%s",
10432 green, buf[0], red, buf[1], default_color);
10433 } else {
10434 snprintf(progressbar, sizeof(progressbar), "%s", "keys scanned");
10435 }
10436
10437 line_count += cleanPrintfln("%6.2f%% %s", completion_pct * 100, progressbar);
10438 line_count += cleanPrintfln("Keys sampled: %llu", sampled);
10439
10440 return line_count;
10441}
10442
10443static int displayKeyStatsSizeType(dict *memkeys_types_dict) {
10444 dictIterator di;
10445 dictEntry *de;
10446 int line_count = 0;
10447 char buf[256];
10448
10449 line_count += cleanPrintfln("--- Top size per type ---");
10450 dictInitIterator(&di, memkeys_types_dict);
10451 while ((de = dictNext(&di))) {
10452 typeinfo *type = dictGetVal(de);
10453 if (type->biggest_key) {
10454 line_count += cleanPrintfln("%-10s %s is %s",
10455 type->name, type->biggest_key,
10456 bytesToHuman(buf, sizeof(buf),type->biggest));
10457 }
10458 }
10459 dictResetIterator(&di);
10460
10461 return line_count;
10462}
10463
10464static int displayKeyStatsLengthType(dict *bigkeys_types_dict) {
10465 dictIterator di;
10466 dictEntry *de;
10467 int line_count = 0;
10468 char buf[256];
10469
10470 line_count += cleanPrintfln("--- Top length and cardinality per type ---");
10471 dictInitIterator(&di, bigkeys_types_dict);
10472 while ((de = dictNext(&di))) {
10473 typeinfo *type = dictGetVal(de);
10474 if (type->biggest_key) {
10475 if (!strcmp(type->sizeunit, "bytes")) {
10476 bytesToHuman(buf, sizeof(buf), type->biggest);
10477 } else {
10478 snprintf(buf, sizeof(buf), "%llu %s", type->biggest, type->sizeunit);
10479 }
10480 line_count += cleanPrintfln("%-10s %s has %s", type->name, type->biggest_key, buf);
10481 }
10482 }
10483 dictResetIterator(&di);
10484
10485 return line_count;
10486}
10487
10488static int displayKeyStatsSizeDist(struct hdr_histogram *keysize_histogram) {
10489 int line_count = 0;
10490 double percentile;
10491 char size[32], mean[32], stddev[32];
10492 struct hdr_iter iter;
10493 int64_t last_displayed_cumulative_count = 0;
10494
10495 hdr_iter_percentile_init(&iter, keysize_histogram, 1);
10496
10497 line_count += cleanPrintfln("Key size Percentile Total keys");
10498 line_count += cleanPrintfln("-------- ---------- -----------");
10499
10500 while (hdr_iter_next(&iter)) {
10501 /* Skip repeat in hdr_histogram cumulative_count, and set the last line
10502 * to 100% when total_count is reached. For instance:
10503 * 140.68K 99.9969% 50013
10504 * 140.68K 99.9977% 50013
10505 * 2.04G 99.9985% 50014
10506 * 2.04G 100.0000% 50014
10507 * Will display:
10508 * 140.68K 99.9969% 50013
10509 * 2.04G 100.0000% 50014 */
10510
10511 if (iter.cumulative_count != last_displayed_cumulative_count) {
10512 if (iter.cumulative_count == iter.h->total_count) {
10513 percentile = 100;
10514 } else {
10515 percentile = iter.specifics.percentiles.percentile;
10516 }
10517
10518 line_count += cleanPrintfln("%8s %9.4f%% %11lld",
10519 bytesToHuman(size, sizeof(size), iter.highest_equivalent_value),
10520 percentile,
10521 iter.cumulative_count);
10522
10523 last_displayed_cumulative_count = iter.cumulative_count;
10524 }
10525 }
10526
10527 bytesToHuman(mean, sizeof(mean),hdr_mean(keysize_histogram));
10528 bytesToHuman(stddev, sizeof(stddev),hdr_stddev(keysize_histogram));
10529 line_count += cleanPrintfln("Note: 0.01%% size precision, Mean: %s, StdDeviation: %s", mean, stddev);
10530
10531 return line_count;
10532}
10533
10534static int displayKeyStatsType(unsigned long long sampled,
10535 dict *memkeys_types_dict,
10536 dict *bigkeys_types_dict)
10537{
10538 dictIterator di;
10539 dictEntry *de;
10540 int line_count = 0;
10541 char total_size[64], size_avg[64], total_length[64], length_avg[64];
10542
10543 line_count += cleanPrintfln("Type Total keys Keys %% Tot size Avg size Total length/card Avg ln/card");
10544 line_count += cleanPrintfln("--------- ------------ ------- -------- -------- ------------------ -----------");
10545
10546 dictInitIterator(&di, memkeys_types_dict);
10547 while ((de = dictNext(&di))) {
10548 typeinfo *memkey_type = dictGetVal(de);
10549 if (memkey_type->count) {
10550 /* Key count, percentage, memkeys info */
10551 bytesToHuman(total_size, sizeof(total_size), memkey_type->totalsize);
10552 bytesToHuman(size_avg, sizeof(size_avg), memkey_type->totalsize/memkey_type->count);
10553
10554 strncpy(total_length, " - ", sizeof(total_length));
10555 strncpy(length_avg, " - ", sizeof(length_avg));
10556
10557 /* bigkeys info */
10558 dictEntry *bk_de = dictFind(bigkeys_types_dict, memkey_type->name);
10559 if (bk_de) { /* If we have it in memkeys it should be in bigkeys */
10560 typeinfo *bigkey_type = dictGetVal(bk_de);
10561 if (bigkey_type->sizecmd && bigkey_type->count) {
10562 double avg = (double)bigkey_type->totalsize/bigkey_type->count;
10563 if (!strcmp(bigkey_type->sizeunit, "bytes")) {
10564 bytesToHuman(total_length, sizeof(total_length), bigkey_type->totalsize);
10565 bytesToHuman(length_avg, sizeof(length_avg), (long long)round(avg)); /* better than truncating */
10566 } else {
10567 snprintf(total_length, sizeof(total_length), "%llu %s", bigkey_type->totalsize, bigkey_type->sizeunit);
10568 snprintf(length_avg, sizeof(length_avg), "%.2f", avg);
10569 }
10570 }
10571 }
10572 /* Print the line for the given Redis type */
10573 line_count += cleanPrintfln("%-10s %11llu %6.2f%% %8s %8s %18s %11s",
10574 memkey_type->name, memkey_type->count,
10575 sampled ? 100 * (double)memkey_type->count/sampled : 0,
10576 total_size, size_avg, total_length, length_avg);
10577 }
10578 }
10579 dictResetIterator(&di);
10580
10581 return line_count;
10582}
10583
10584typedef struct key_info {
10585 unsigned long long size;
10586 char type_name[10]; /* Key type name seems to be 9 char max + \0 */
10587 sds key_name;
10588} key_info;
10589
10590static int displayKeyStatsTopSizes(list *top_key_sizes, unsigned long top_sizes_limit) {
10591 int line_count = 0, i = 0;
10592
10593 line_count += cleanPrintfln("--- Top %llu key sizes ---", top_sizes_limit);
10594 char buffer[32];
10595 listIter iter;
10596 listNode *node;
10597 listRewind(top_key_sizes, &iter);
10598 while ((node = listNext(&iter)) != NULL) {
10599 key_info *key = (key_info*) listNodeValue(node);
10600 line_count += cleanPrintfln("%3d %8s %-10s %s", ++i, bytesToHuman(buffer, sizeof(buffer), key->size),
10601 key->type_name, key->key_name);
10602 }
10603
10604 return line_count;
10605}
10606
10607static key_info *createKeySizeInfo(char *key_name, size_t key_name_len, char *key_type, unsigned long long size) {
10608 key_info *key = zmalloc(sizeof(key_info));
10609 key->size = size;
10610 snprintf(key->type_name, sizeof(key->type_name), "%s", key_type);
10611 key->key_name = sdscatrepr(sdsempty(), key_name, key_name_len);
10612 if (!key->key_name) {
10613 fprintf(stderr, "Failed to allocate memory for key name.\n");
10614 exit(1);
10615 }
10616 return key;
10617}
10618
10619/* Insert key info in topkeys sorted by size (from high to low size).
10620 * Keep a maximum of config.top_sizes_limit items in topkeys list.
10621 * key_name and type_name are copied.
10622 * Return: 0 size was not added (too small), 1 size was inserted. */
10623static int updateTopSizes(char *key_name, size_t key_name_len, unsigned long long key_size,
10624 char *type_name, list *topkeys, unsigned long top_sizes_limit)
10625{
10626 listNode *node;
10627 listIter iter;
10628 key_info *new_node;
10629
10630 /* Check if we do not need to add to the list */
10631 if (top_sizes_limit != 0 &&
10632 topkeys->len == top_sizes_limit &&
10633 key_size <= ((key_info*)topkeys->tail->value)->size){
10634 return 0;
10635 }
10636
10637 /* Find where to insert the new key size */
10638 listRewind(topkeys, &iter);
10639 do {
10640 node = listNext(&iter);
10641 } while (node != NULL && key_size <= ((key_info*)node->value)->size);
10642
10643 new_node = createKeySizeInfo(key_name, key_name_len, type_name, key_size);
10644 if (node) {
10645 /* Insert before the node */
10646 listInsertNode(topkeys, node, new_node, 0);
10647 } else {
10648 /* Insert as the last node */
10649 listAddNodeTail(topkeys, new_node);
10650 }
10651
10652 /* Trim to stay within the limit */
10653 if (topkeys->len == top_sizes_limit + 1) {
10654 sdsfree(((key_info*)topkeys->tail->value)->key_name);
10655 listDelNode(topkeys, topkeys->tail); /* list->free is set */
10656 }
10657
10658 return 1;
10659}
10660
10661static void displayKeyStats(unsigned long long sampled, unsigned long long total_keys,
10662 unsigned long long total_size, dict *memkeys_types_dict,
10663 dict *bigkeys_types_dict, list *top_key_sizes,
10664 unsigned long top_sizes_limit, int move_cursor_up)
10665{
10666 int line_count = 0;
10667 char buf[256];
10668
10669 line_count += displayKeyStatsProgressbar(sampled, total_keys);
10670 line_count += cleanPrintfln("Keys size: %s", bytesToHuman(buf, sizeof(buf), total_size));
10671 line_count += cleanPrintfln("");
10672 line_count += displayKeyStatsTopSizes(top_key_sizes, top_sizes_limit);
10673 line_count += cleanPrintfln("");
10674 line_count += displayKeyStatsSizeType(memkeys_types_dict);
10675 line_count += cleanPrintfln("");
10676 line_count += displayKeyStatsLengthType(bigkeys_types_dict);
10677
10678 /* If we need to refresh the stats */
10679 if (move_cursor_up) {
10680 printf("\033[%dA\r", line_count);
10681 }
10682
10683 fflush(stdout);
10684}
10685
10686static void updateKeyType(redisReply *element, unsigned long long size, typeinfo *type) {
10687 type->totalsize += size;
10688 type->count++;
10689
10690 if (type->biggest<size) {
10691 /* Keep track of biggest key name for this type */
10692 if (type->biggest_key)
10693 sdsfree(type->biggest_key);
10694 type->biggest_key = sdsnewlen(element->str, element->len);
10695 if (!type->biggest_key) {
10696 fprintf(stderr, "Failed to allocate memory for key!\n");
10697 exit(1);
10698 }
10699 /* Keep track of the biggest size for this type */
10700 type->biggest = size;
10701 }
10702}
10703
10704static void keyStats(long long memkeys_samples, unsigned long long cursor, unsigned long top_sizes_limit) {
10705 unsigned long long sampled = 0, total_keys, total_size = 0, it = 0, scan_loops = 0;
10706 unsigned long long *memkeys_sizes = NULL, *bigkeys_sizes = NULL;
10707 redisReply *reply, *keys;
10708 unsigned int array_size = 0, i;
10709 typeinfo **memkeys_types = NULL, **bigkeys_types = NULL;
10710 list *top_sizes;
10711 long long refresh_time = mstime();
10712
10713 if (cursor != 0) {
10714 it = cursor;
10715 }
10716
10717 if ((top_sizes = listCreate()) == NULL) {
10718 fprintf(stderr, "top_sizes list creation failed.\n");
10719 exit(1);
10720 }
10721 top_sizes->free = zfree;
10722
10723 dict *memkeys_types_dict = dictCreate(&typeinfoDictType);
10724 typeinfo_add(memkeys_types_dict, "string", &type_string);
10725 typeinfo_add(memkeys_types_dict, "list", &type_list);
10726 typeinfo_add(memkeys_types_dict, "set", &type_set);
10727 typeinfo_add(memkeys_types_dict, "hash", &type_hash);
10728 typeinfo_add(memkeys_types_dict, "zset", &type_zset);
10729 typeinfo_add(memkeys_types_dict, "stream", &type_stream);
10730
10731 /* We could use only one typeinfo dictionary if we add new fields to save
10732 * both memkey and bigkey info. Not sure it would make sense in findBigKeys(). */
10733 dict *bigkeys_types_dict = dictCreate(&typeinfoDictType);
10734 typeinfo_add(bigkeys_types_dict, "string", &type_string);
10735 typeinfo_add(bigkeys_types_dict, "list", &type_list);
10736 typeinfo_add(bigkeys_types_dict, "set", &type_set);
10737 typeinfo_add(bigkeys_types_dict, "hash", &type_hash);
10738 typeinfo_add(bigkeys_types_dict, "zset", &type_zset);
10739 typeinfo_add(bigkeys_types_dict, "stream", &type_stream);
10740
10741 size_dist key_length_dist;
10742 size_dist_entry distribution[] = {
10743 {1<<5, 0}, /* 32 B (sds) */
10744 {1<<8, 0}, /* 256 B (sds) */
10745 {1<<16, 0}, /* 64 KB (sds and Redis Enterprise key name max length) */
10746 {1024*1024, 0}, /* 1 MB */
10747 {16*1024*1024, 0}, /* 16 MB */
10748 {128*1024*1024, 0}, /* 128 MB */
10749 {512*1024*1024, 0}, /* 512 MB (max String size) */
10750 {0, 0}, /* Sizes above the last entry */
10751 };
10752 sizeDistInit(&key_length_dist, distribution);
10753
10754 struct hdr_histogram *keysize_histogram;
10755 /* Record max of 1TB for a key size should cover all keys.
10756 * significant_figures == 4 (0.01% precision on key size) */
10757 if (hdr_init(1, 1ULL*1024*1024*1024*1024, 4, &keysize_histogram)) {
10758 fprintf(stderr, "Keystats hdr init error\n");
10759 exit(1);
10760 }
10761
10762 signal(SIGINT, longStatLoopModeStop);
10763
10764 /* Total keys pre scanning */
10765 total_keys = getDbSize();
10766
10767 /* Status message */
10768 printf("\n# Scanning the entire keyspace to find the biggest keys and distribution information.\n");
10769 printf("# Use -i 0.1 to sleep 0.1 sec per 100 SCAN commands (not usually needed).\n");
10770 printf("# Use --cursor <n> to start the scan at the cursor <n> (usually after a Ctrl-C).\n");
10771 printf("# Use --top <n> to display <n> top key sizes (default is 10).\n");
10772 printf("# Ctrl-C to stop the scan.\n\n");
10773
10774 /* Use readonly in cluster */
10775 sendReadOnly();
10776
10777 /* SCAN loop */
10778 do {
10779 /* Grab some keys and point to the keys array */
10780 reply = sendScan(&it);
10781 scan_loops++;
10782 keys = reply->element[1];
10783
10784 /* Reallocate our type and size array if we need to */
10785 if (keys->elements > array_size) {
10786 memkeys_types = zrealloc(memkeys_types, sizeof(typeinfo*)*keys->elements);
10787 memkeys_sizes = zrealloc(memkeys_sizes, sizeof(unsigned long long)*keys->elements);
10788
10789 bigkeys_types = zrealloc(bigkeys_types, sizeof(typeinfo*)*keys->elements);
10790 bigkeys_sizes = zrealloc(bigkeys_sizes, sizeof(unsigned long long)*keys->elements);
10791
10792 if (!memkeys_types || !memkeys_sizes || !bigkeys_types || !bigkeys_sizes) {
10793 fprintf(stderr, "Failed to allocate storage for keys!\n");
10794 exit(1);
10795 }
10796
10797 array_size = keys->elements;
10798 }
10799
10800 /* Retrieve types and sizes for memkeys */
10801 getKeyTypes(memkeys_types_dict, keys, memkeys_types);
10802 getKeySizes(keys, memkeys_types, memkeys_sizes, 1, memkeys_samples);
10803
10804 /* Retrieve types and sizes for bigkeys */
10805 getKeyTypes(bigkeys_types_dict, keys, bigkeys_types);
10806 getKeySizes(keys, bigkeys_types, bigkeys_sizes, 0, memkeys_samples);
10807
10808 for (i=0; i<keys->elements; i++) {
10809 /* Skip keys that disappeared between SCAN and TYPE */
10810 if (!memkeys_types[i] || !bigkeys_types[i]) {
10811 continue;
10812 }
10813
10814 total_size += memkeys_sizes[i];
10815 sampled++;
10816
10817 updateTopSizes(keys->element[i]->str, keys->element[i]->len, memkeys_sizes[i],
10818 memkeys_types[i]->name, top_sizes, top_sizes_limit);
10819 updateKeyType(keys->element[i], memkeys_sizes[i], memkeys_types[i]);
10820 updateKeyType(keys->element[i], bigkeys_sizes[i], bigkeys_types[i]);
10821
10822 /* Key Size distribution */
10823 if (hdr_record_value(keysize_histogram, memkeys_sizes[i]) == 0) {
10824 fprintf(stderr, "Value %llu not added in the hdr histogram.\n", memkeys_sizes[i]);
10825 }
10826
10827 /* Key length distribution */
10828 addSizeDist(&key_length_dist, keys->element[i]->len);
10829 }
10830
10831 /* Refresh keystats info on regular basis */
10832 if (mstime() > refresh_time + REFRESH_INTERVAL && IS_TTY_OR_FAKETTY()) {
10833 displayKeyStats(sampled, total_keys, total_size, memkeys_types_dict, bigkeys_types_dict,
10834 top_sizes, top_sizes_limit, 1);
10835 refresh_time = mstime();
10836 }
10837
10838 /* Sleep if we've been directed to do so */
10839 if (config.interval && (scan_loops % 100) == 0) {
10840 usleep(config.interval);
10841 }
10842
10843 freeReplyObject(reply);
10844 } while(force_cancel_loop == 0 && it != 0);
10845
10846 displayKeyStats(sampled, total_keys, total_size, memkeys_types_dict, bigkeys_types_dict, top_sizes,
10847 top_sizes_limit, 0);
10848
10849 /* Additional data at the end of the SCAN loop.
10850 * Using cleanPrintfln in case we want to print during the SCAN loop. */
10851 cleanPrintfln("");
10852 displayKeyStatsSizeDist(keysize_histogram);
10853 cleanPrintfln("");
10854 displayKeyStatsLengthDist(&key_length_dist);
10855 cleanPrintfln("");
10856 displayKeyStatsType(sampled, memkeys_types_dict, bigkeys_types_dict);
10857
10858 if (it != 0) {
10859 printf("\n");
10860 printf("Scan interrupted:\n");
10861 printf("Use 'redis-cli --keystats --cursor %llu' to restart from the last cursor.\n", it);
10862 }
10863
10864 if (memkeys_types) zfree(memkeys_types);
10865 if (bigkeys_types) zfree(bigkeys_types);
10866 if (memkeys_sizes) zfree(memkeys_sizes);
10867 if (bigkeys_sizes) zfree(bigkeys_sizes);
10868 dictRelease(memkeys_types_dict);
10869 dictRelease(bigkeys_types_dict);
10870 hdr_close(keysize_histogram);
10871
10872 /* sdsfree before listRelease */
10873 listIter iter;
10874 listNode *node;
10875 listRewind(top_sizes, &iter);
10876 while ((node = listNext(&iter)) != NULL) {
10877 key_info *key = (key_info*) listNodeValue(node);
10878 sdsfree(key->key_name);
10879 }
10880 listRelease(top_sizes); /* list->free is set */
10881
10882 exit(0);
10883}
10884
10885/*------------------------------------------------------------------------------
10886 * Program main()
10887 *--------------------------------------------------------------------------- */
10888
10889int main(int argc, char **argv) {
10890 int firstarg;
10891 struct timeval tv;
10892
10893 memset(&config.sslconfig, 0, sizeof(config.sslconfig));
10894 config.conn_info.hostip = sdsnew("127.0.0.1");
10895 config.conn_info.hostport = 6379;
10896 config.connect_timeout.tv_sec = 0;
10897 config.connect_timeout.tv_usec = 0;
10898 config.hostsocket = NULL;
10899 config.repeat = 1;
10900 config.interval = 0;
10901 config.dbnum = 0;
10902 config.conn_info.input_dbnum = 0;
10903 config.interactive = 0;
10904 config.shutdown = 0;
10905 config.monitor_mode = 0;
10906 config.pubsub_mode = 0;
10907 config.blocking_state_aborted = 0;
10908 config.vset_recall_mode = 0;
10909 config.vset_recall_key = NULL;
10910 config.vset_recall_ele_count = 1;
10911 config.vset_recall_vsim_count = 100;
10912 config.vset_recall_vsim_ef = 500;
10913 config.latency_mode = 0;
10914 config.latency_dist_mode = 0;
10915 config.latency_history = 0;
10916 config.lru_test_mode = 0;
10917 config.lru_test_sample_size = 0;
10918 config.cluster_mode = 0;
10919 config.cluster_send_asking = 0;
10920 config.slave_mode = 0;
10921 config.getrdb_mode = 0;
10922 config.get_functions_rdb_mode = 0;
10923 config.stat_mode = 0;
10924 config.scan_mode = 0;
10925 config.count = 10;
10926 config.intrinsic_latency_mode = 0;
10927 config.pattern = NULL;
10928 config.rdb_filename = NULL;
10929 config.pipe_mode = 0;
10930 config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
10931 config.bigkeys = 0;
10932 config.memkeys = 0;
10933 config.keystats = 0;
10934 config.cursor = 0;
10935 config.top_sizes_limit = 10;
10936 config.hotkeys = 0;
10937 config.stdin_lastarg = 0;
10938 config.stdin_tag_arg = 0;
10939 config.stdin_tag_name = NULL;
10940 config.conn_info.auth = NULL;
10941 config.askpass = 0;
10942 config.conn_info.user = NULL;
10943 config.eval = NULL;
10944 config.eval_ldb = 0;
10945 config.eval_ldb_end = 0;
10946 config.eval_ldb_sync = 0;
10947 config.enable_ldb_on_eval = 0;
10948 config.last_cmd_type = -1;
10949 config.last_reply = NULL;
10950 config.verbose = 0;
10951 config.set_errcode = 0;
10952 config.no_auth_warning = 0;
10953 config.in_multi = 0;
10954 config.server_version = NULL;
10955 config.prefer_ipv4 = 0;
10956 config.prefer_ipv6 = 0;
10957 config.cluster_manager_command.name = NULL;
10958 config.cluster_manager_command.argc = 0;
10959 config.cluster_manager_command.argv = NULL;
10960 config.cluster_manager_command.stdin_arg = NULL;
10961 config.cluster_manager_command.flags = 0;
10962 config.cluster_manager_command.replicas = 0;
10963 config.cluster_manager_command.from = NULL;
10964 config.cluster_manager_command.to = NULL;
10965 config.cluster_manager_command.from_user = NULL;
10966 config.cluster_manager_command.from_pass = NULL;
10967 config.cluster_manager_command.from_askpass = 0;
10968 config.cluster_manager_command.weight = NULL;
10969 config.cluster_manager_command.weight_argc = 0;
10970 config.cluster_manager_command.slots = 0;
10971 config.cluster_manager_command.timeout = CLUSTER_MANAGER_MIGRATE_TIMEOUT;
10972 config.cluster_manager_command.pipeline = CLUSTER_MANAGER_MIGRATE_PIPELINE;
10973 config.cluster_manager_command.threshold =
10974 CLUSTER_MANAGER_REBALANCE_THRESHOLD;
10975 config.cluster_manager_command.backup_dir = NULL;
10976 pref.hints = 1;
10977
10978 spectrum_palette = spectrum_palette_color;
10979 spectrum_palette_size = spectrum_palette_color_size;
10980
10981 if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL)) {
10982 config.output = OUTPUT_RAW;
10983 config.push_output = 0;
10984 } else {
10985 config.output = OUTPUT_STANDARD;
10986 config.push_output = 1;
10987 }
10988 config.mb_delim = sdsnew("\n");
10989 config.cmd_delim = sdsnew("\n");
10990
10991 firstarg = parseOptions(argc,argv);
10992 argc -= firstarg;
10993 argv += firstarg;
10994
10995 parseEnv();
10996
10997 if (config.askpass) {
10998 config.conn_info.auth = askPassword("Please input password: ");
10999 }
11000
11001 if (config.cluster_manager_command.from_askpass) {
11002 config.cluster_manager_command.from_pass = askPassword(
11003 "Please input import source node password: ");
11004 }
11005
11006#ifdef USE_OPENSSL
11007 if (config.tls) {
11008 cliSecureInit();
11009 }
11010#endif
11011
11012 gettimeofday(&tv, NULL);
11013 init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid());
11014
11015 /* Cluster Manager mode */
11016 if (CLUSTER_MANAGER_MODE()) {
11017 clusterManagerCommandProc *proc = validateClusterManagerCommand();
11018 if (!proc) {
11019 exit(1);
11020 }
11021 clusterManagerMode(proc);
11022 }
11023
11024 /* Latency mode */
11025 if (config.latency_mode) {
11026 if (cliConnect(0) == REDIS_ERR) exit(1);
11027 latencyMode();
11028 }
11029
11030 /* Latency distribution mode */
11031 if (config.latency_dist_mode) {
11032 if (cliConnect(0) == REDIS_ERR) exit(1);
11033 latencyDistMode();
11034 }
11035
11036 /* Latency mode */
11037 if (config.vset_recall_mode) {
11038 if (cliConnect(0) == REDIS_ERR) exit(1);
11039 vsetRecallMode();
11040 }
11041
11042 /* Slave mode */
11043 if (config.slave_mode) {
11044 if (cliConnect(0) == REDIS_ERR) exit(1);
11045 sendCapa();
11046 sendReplconf("rdb-filter-only", "");
11047 slaveMode(1);
11048 }
11049
11050 /* Get RDB/functions mode. */
11051 if (config.getrdb_mode || config.get_functions_rdb_mode) {
11052 if (cliConnect(0) == REDIS_ERR) exit(1);
11053 sendCapa();
11054 sendRdbOnly();
11055 if (config.get_functions_rdb_mode && !sendReplconf("rdb-filter-only", "functions")) {
11056 fprintf(stderr, "Failed requesting functions only RDB from server, aborting\n");
11057 exit(1);
11058 }
11059 getRDB(NULL);
11060 }
11061
11062 /* Pipe mode */
11063 if (config.pipe_mode) {
11064 if (cliConnect(0) == REDIS_ERR) exit(1);
11065 pipeMode();
11066 }
11067
11068 /* Find big keys */
11069 if (config.bigkeys) {
11070 if (cliConnect(0) == REDIS_ERR) exit(1);
11071 findBigKeys(0, 0);
11072 }
11073
11074 /* Find large keys */
11075 if (config.memkeys) {
11076 if (cliConnect(0) == REDIS_ERR) exit(1);
11077 findBigKeys(1, config.memkeys_samples);
11078 }
11079
11080 /* Find big and large keys */
11081 if (config.keystats) {
11082 if (cliConnect(0) == REDIS_ERR) exit(1);
11083 keyStats(config.memkeys_samples, config.cursor, config.top_sizes_limit);
11084 }
11085
11086 /* Find hot keys */
11087 if (config.hotkeys) {
11088 if (cliConnect(0) == REDIS_ERR) exit(1);
11089 findHotKeys();
11090 }
11091
11092 /* Stat mode */
11093 if (config.stat_mode) {
11094 if (cliConnect(0) == REDIS_ERR) exit(1);
11095 if (config.interval == 0) config.interval = 1000000;
11096 statMode();
11097 }
11098
11099 /* Scan mode */
11100 if (config.scan_mode) {
11101 if (cliConnect(0) == REDIS_ERR) exit(1);
11102 scanMode();
11103 }
11104
11105 /* LRU test mode */
11106 if (config.lru_test_mode) {
11107 if (cliConnect(0) == REDIS_ERR) exit(1);
11108 LRUTestMode();
11109 }
11110
11111 /* Intrinsic latency mode */
11112 if (config.intrinsic_latency_mode) intrinsicLatencyMode();
11113
11114 /* Print command-line hint for an input prefix string */
11115 if (config.test_hint) {
11116 testHint(config.test_hint);
11117 }
11118 /* Run test suite for command-line hints */
11119 if (config.test_hint_file) {
11120 testHintSuite(config.test_hint_file);
11121 }
11122
11123 /* Start interactive mode when no command is provided */
11124 if (argc == 0 && !config.eval) {
11125 /* Ignore SIGPIPE in interactive mode to force a reconnect */
11126 signal(SIGPIPE, SIG_IGN);
11127 signal(SIGINT, sigIntHandler);
11128
11129 /* Note that in repl mode we don't abort on connection error.
11130 * A new attempt will be performed for every command send. */
11131 cliConnect(0);
11132 repl();
11133 }
11134
11135 /* Otherwise, we have some arguments to execute */
11136 if (config.eval) {
11137 if (cliConnect(0) != REDIS_OK) exit(1);
11138 return evalMode(argc,argv);
11139 } else {
11140 cliConnect(CC_QUIET);
11141 return noninteractive(argc,argv);
11142 }
11143}