diff options
-rw-r--r-- | src/redis-cli.c | 98 | ||||
-rw-r--r-- | tests/integration/redis-cli.tcl | 48 |
2 files changed, 123 insertions, 23 deletions
diff --git a/src/redis-cli.c b/src/redis-cli.c index 24e8fe562..2fb7d9ef0 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -179,6 +179,7 @@ typedef struct clusterManagerCommand { char *name; int argc; char **argv; + sds stdin_arg; /* arg from stdin. (-X option) */ int flags; int replicas; char *from; @@ -196,7 +197,7 @@ typedef struct clusterManagerCommand { int from_askpass; } clusterManagerCommand; -static void createClusterManagerCommand(char *cmdname, int argc, char **argv); +static int createClusterManagerCommand(char *cmdname, int argc, char **argv); static redisContext *context; @@ -235,7 +236,9 @@ static struct config { int memkeys; unsigned memkeys_samples; int hotkeys; - int stdinarg; /* get last arg from stdin. (-x option) */ + int stdin_lastarg; /* get last arg from stdin. (-x option) */ + int stdin_tag_arg; /* get <tag> arg from stdin. (-X option) */ + char *stdin_tag_name; /* Placeholder(tag name) for user input. */ int askpass; int quoted_input; /* Force input args to be treated as quoted strings */ int output; /* output mode, see OUTPUT_* defines */ @@ -1562,7 +1565,10 @@ static int parseOptions(int argc, char **argv) { } else if (!strcmp(argv[i],"--help")) { usage(0); } else if (!strcmp(argv[i],"-x")) { - config.stdinarg = 1; + config.stdin_lastarg = 1; + } else if (!strcmp(argv[i], "-X") && !lastarg) { + config.stdin_tag_arg = 1; + config.stdin_tag_name = argv[++i]; } else if (!strcmp(argv[i],"-p") && !lastarg) { config.conn_info.hostport = atoi(argv[++i]); } else if (!strcmp(argv[i],"-s") && !lastarg) { @@ -1678,7 +1684,8 @@ static int parseOptions(int argc, char **argv) { int j = i; while (j < argc && argv[j][0] != '-') j++; if (j > i) j--; - createClusterManagerCommand(cmd, j - i, argv + i + 1); + int err = createClusterManagerCommand(cmd, j - i, argv + i + 1); + if (err) exit(err); i = j; } else if (!strcmp(argv[i],"--cluster") && lastarg) { usage(1); @@ -1841,6 +1848,11 @@ static int parseOptions(int argc, char **argv) { " line interface may not be safe.\n", stderr); } + if (config.stdin_lastarg && config.stdin_tag_arg) { + fprintf(stderr, "Options -x and -X are mutually exclusive.\n"); + exit(1); + } + return i; } @@ -1885,7 +1897,8 @@ static void usage(int err) { " -n <db> Database number.\n" " -2 Start session in RESP2 protocol mode.\n" " -3 Start session in RESP3 protocol mode.\n" -" -x Read last argument from STDIN.\n" +" -x Read last argument from STDIN (see example below).\n" +" -X Read <tag> argument from STDIN (see example below).\n" " -d <delimiter> Delimiter between response bulks for raw formatting (default: \\n).\n" " -D <delimiter> Delimiter between responses for raw formatting (default: \\n).\n" " -c Enable cluster mode (follow -ASK and -MOVED redirections).\n" @@ -1975,7 +1988,7 @@ static void usage(int err) { "\n" "Examples:\n" " cat /etc/passwd | redis-cli -x set mypasswd\n" -" redis-cli get mypasswd\n" +" redis-cli -D \"\" --raw dump key > key.dump && redis-cli -X dump_tag restore key2 0 dump_tag replace < key.dump\n" " redis-cli -r 100 lpush mylist x\n" " redis-cli -r 100 -i 1 info | grep used_memory_human:\n" " redis-cli --quoted-input set '\"null-\\x00-separated\"' value\n" @@ -2290,14 +2303,33 @@ static void repl(void) { static int noninteractive(int argc, char **argv) { int retval = 0; sds *sds_args = getSdsArrayFromArgv(argc, argv, config.quoted_input); + if (!sds_args) { printf("Invalid quoted string\n"); return 1; } - if (config.stdinarg) { + + if (config.stdin_lastarg) { sds_args = sds_realloc(sds_args, (argc + 1) * sizeof(sds)); sds_args[argc] = readArgFromStdin(); argc++; + } else if (config.stdin_tag_arg) { + int i = 0, tag_match = 0; + + for (; i < argc; i++) { + if (strcmp(config.stdin_tag_name, sds_args[i]) != 0) continue; + + tag_match = 1; + sdsfree(sds_args[i]); + sds_args[i] = readArgFromStdin(); + break; + } + + if (!tag_match) { + sdsfreesplitres(sds_args, argc); + fprintf(stderr, "Using -X option but stdin tag not match.\n"); + return 1; + } } retval = issueCommand(argc, sds_args); @@ -2577,14 +2609,41 @@ clusterManagerOptionDef clusterManagerOptions[] = { static void getRDB(clusterManagerNode *node); -static void createClusterManagerCommand(char *cmdname, int argc, char **argv) { +static int createClusterManagerCommand(char *cmdname, int argc, char **argv) { clusterManagerCommand *cmd = &config.cluster_manager_command; cmd->name = cmdname; cmd->argc = argc; cmd->argv = argc ? argv : NULL; if (isColorTerm()) cmd->flags |= CLUSTER_MANAGER_CMD_FLAG_COLOR; -} + if (config.stdin_lastarg) { + char **new_argv = zmalloc(sizeof(char*) * (cmd->argc+1)); + memcpy(new_argv, cmd->argv, sizeof(char*) * cmd->argc); + + cmd->stdin_arg = readArgFromStdin(); + new_argv[cmd->argc++] = cmd->stdin_arg; + cmd->argv = new_argv; + } else if (config.stdin_tag_arg) { + int i = 0, tag_match = 0; + cmd->stdin_arg = readArgFromStdin(); + + for (; i < argc; i++) { + if (strcmp(argv[i], config.stdin_tag_name) != 0) continue; + + tag_match = 1; + cmd->argv[i] = (char *)cmd->stdin_arg; + break; + } + + if (!tag_match) { + sdsfree(cmd->stdin_arg); + fprintf(stderr, "Using -X option but stdin tag not match.\n"); + return 1; + } + } + + return 0; +} static clusterManagerCommandProc *validateClusterManagerCommand(void) { int i, commands_count = sizeof(clusterManagerCommands) / @@ -5591,12 +5650,18 @@ static void clusterManagerMode(clusterManagerCommandProc *proc) { int argc = config.cluster_manager_command.argc; char **argv = config.cluster_manager_command.argv; cluster_manager.nodes = NULL; - if (!proc(argc, argv)) goto cluster_manager_err; - freeClusterManager(); - exit(0); -cluster_manager_err: + int success = proc(argc, argv); + + /* Initialized in createClusterManagerCommand. */ + if (config.stdin_lastarg) { + zfree(config.cluster_manager_command.argv); + sdsfree(config.cluster_manager_command.stdin_arg); + } else if (config.stdin_tag_arg) { + sdsfree(config.cluster_manager_command.stdin_arg); + } freeClusterManager(); - exit(1); + + exit(success ? 0 : 1); } /* Cluster Manager Commands */ @@ -8355,7 +8420,9 @@ int main(int argc, char **argv) { config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT; config.bigkeys = 0; config.hotkeys = 0; - config.stdinarg = 0; + config.stdin_lastarg = 0; + config.stdin_tag_arg = 0; + config.stdin_tag_name = NULL; config.conn_info.auth = NULL; config.askpass = 0; config.conn_info.user = NULL; @@ -8372,6 +8439,7 @@ int main(int argc, char **argv) { config.cluster_manager_command.name = NULL; config.cluster_manager_command.argc = 0; config.cluster_manager_command.argv = NULL; + config.cluster_manager_command.stdin_arg = NULL; config.cluster_manager_command.flags = 0; config.cluster_manager_command.replicas = 0; config.cluster_manager_command.from = NULL; diff --git a/tests/integration/redis-cli.tcl b/tests/integration/redis-cli.tcl index 993841217..4b9ce38d1 100644 --- a/tests/integration/redis-cli.tcl +++ b/tests/integration/redis-cli.tcl @@ -108,12 +108,20 @@ start_server {tags {"cli"}} { _run_cli [srv host] [srv port] $::dbnum {} {*}$args } - proc run_cli_with_input_pipe {cmd args} { - _run_cli [srv host] [srv port] $::dbnum [list pipe $cmd] -x {*}$args + proc run_cli_with_input_pipe {mode cmd args} { + if {$mode == "x" } { + _run_cli [srv host] [srv port] $::dbnum [list pipe $cmd] -x {*}$args + } elseif {$mode == "X"} { + _run_cli [srv host] [srv port] $::dbnum [list pipe $cmd] -X tag {*}$args + } } - proc run_cli_with_input_file {path args} { - _run_cli [srv host] [srv port] $::dbnum [list path $path] -x {*}$args + proc run_cli_with_input_file {mode path args} { + if {$mode == "x" } { + _run_cli [srv host] [srv port] $::dbnum [list path $path] -x {*}$args + } elseif {$mode == "X"} { + _run_cli [srv host] [srv port] $::dbnum [list path $path] -X tag {*}$args + } } proc run_cli_host_port_db {host port db args} { @@ -201,14 +209,22 @@ start_server {tags {"cli"}} { } test_tty_cli "Read last argument from pipe" { - assert_equal "OK" [run_cli_with_input_pipe "echo foo" set key] + assert_equal "OK" [run_cli_with_input_pipe x "echo foo" set key] assert_equal "foo\n" [r get key] + + assert_equal "OK" [run_cli_with_input_pipe X "echo foo" set key2 tag] + assert_equal "foo\n" [r get key2] } test_tty_cli "Read last argument from file" { set tmpfile [write_tmpfile "from file"] - assert_equal "OK" [run_cli_with_input_file $tmpfile set key] + + assert_equal "OK" [run_cli_with_input_file x $tmpfile set key] assert_equal "from file" [r get key] + + assert_equal "OK" [run_cli_with_input_file X $tmpfile set key2 tag] + assert_equal "from file" [r get key2] + file delete $tmpfile } @@ -280,14 +296,22 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS } test_nontty_cli "Read last argument from pipe" { - assert_equal "OK" [run_cli_with_input_pipe "echo foo" set key] + assert_equal "OK" [run_cli_with_input_pipe x "echo foo" set key] assert_equal "foo\n" [r get key] + + assert_equal "OK" [run_cli_with_input_pipe X "echo foo" set key2 tag] + assert_equal "foo\n" [r get key2] } test_nontty_cli "Read last argument from file" { set tmpfile [write_tmpfile "from file"] - assert_equal "OK" [run_cli_with_input_file $tmpfile set key] + + assert_equal "OK" [run_cli_with_input_file x $tmpfile set key] assert_equal "from file" [r get key] + + assert_equal "OK" [run_cli_with_input_file X $tmpfile set key2 tag] + assert_equal "from file" [r get key2] + file delete $tmpfile } @@ -399,4 +423,12 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS file delete $cmds } + + test "Options -X with illegal argument" { + assert_error "*-x and -X are mutually exclusive*" {run_cli -x -X tag} + + assert_error "*Unrecognized option or bad number*" {run_cli -X} + + assert_error "*tag not match*" {run_cli_with_input_pipe X "echo foo" set key wrong_tag} + } } |