Unverified Commit 43e736f7 authored by guybe7's avatar guybe7 Committed by GitHub
Browse files

Treat subcommands as commands (#9504)

## Intro

The purpose is to allow having different flags/ACL categories for
subcommands (Example: CONFIG GET is ok-loading but CONFIG SET isn't)

We create a small command table for every command that has subcommands
and each subcommand has its own flags, etc. (same as a "regular" command)

This commit also unites the Redis and the Sentinel command tables

## Affected commands

CONFIG
Used to have "admin ok-loading ok-stale no-script"
Changes:
1. Dropped "ok-loading" in all except GET (this doesn't change behavior since
there were checks in the code doing that)

XINFO
Used to have "read-only random"
Changes:
1. Dropped "random" in all except CONSUMERS

XGROUP
Used to have "write use-memory"
Changes:
1. Dropped "use-memory" in all except CREATE and CREATECONSUMER

COMMAND
No changes.

MEMORY
Used to have "random read-only"
Changes:
1. Dropped "random" in PURGE and USAGE

ACL
Used to have "admin no-script ok-loading ok-stale"
Changes:
1. Dropped "admin" in WHOAMI, GENPASS, and CAT

LATENCY
No changes.

MODULE
No changes.

SLOWLOG
Used to have "admin random ok-loading ok-stale"
Changes:
1. Dropped "random" in RESET

OBJECT
Used to have "read-only random"
Changes:
1. Dropped "random" in ENCODING and REFCOUNT

SCRIPT
Used to have "may-replicate no-script"
Changes:
1. Dropped "may-replicate" in all except FLUSH and LOAD

CLIENT
Used to have "admin no-script random ok-loading ok-stale"
Changes:
1. Dropped "random" in all except INFO and LIST
2. Dropped "admin" in ID, TRACKING, CACHING, GETREDIR, INFO, SETNAME, GETNAME, and REPLY

STRALGO
No changes.

PUBSUB
No changes.

CLUSTER
Changes:
1. Dropped "admin in countkeysinslots, getkeysinslot, info, nodes, keyslot, myid, and slots

SENTINEL
No changes.

(note that DEBUG also fits, but we decided not to convert it since it's for
debugging and anyway undocumented)

## New sub-command
This commit adds another element to the per-command output of COMMAND,
describing the list of subcommands, if any (in the same structure as "regular" commands)
Also, it adds a new subcommand:
```
COMMAND LIST [FILTERBY (MODULE <module-name>|ACLCAT <cat>|PATTERN <pattern>)]
```
which returns a set of all commands (unless filters), but excluding subcommands.

## Module API
A new module API, RM_CreateSubcommand, was added, in order to allow
module writer to define subcommands

## ACL changes:
1. Now, that each subcommand is actually a command, each has its own ACL id.
2. The old mechanism of allowed_subcommands is redundant
(blocking/allowing a subcommand is the same as blocking/allowing a regular command),
but we had to keep it, to support the widespread usage of allowed_subcommands
to block commands with certain args, that aren't subcommands (e.g. "-select +select|0").
3. I have renamed allowed_subcommands to allowed_firstargs to emphasize the difference.
4. Because subcommands are commands in ACL too, you can now use "-" to block subcommands
(e.g. "+client -client|kill"), which wasn't possible in the past.
5. It is also possible to use the allowed_firstargs mechanism with subcommand.
For example: `+config -config|set +config|set|loglevel` will block all CONFIG SET except
for setting the log level.
6. All of the ACL changes above required some amount of refactoring.

## Misc
1. There are two approaches: Either each subcommand has its own function or all
   subcommands use the same function, determining what to do according to argv[0].
   For now, I took the former approaches only with CONFIG and COMMAND,
   while other commands use the latter approach (for smaller blamelog diff).
2. Deleted memoryGetKeys: It is no longer needed because MEMORY USAGE now uses the "range" key spec.
4. Bugfix: GETNAME was missing from CLIENT's help message.
5. Sentinel and Redis now use the same table, with the same function pointer.
   Some commands have a different implementation in Sentinel, so we redirect
   them (these are ROLE, PUBLISH, and INFO).
6. Command stats now show the stats per subcommand (e.g. instead of stats just
   for "config" you will have stats for "config|set", "config|get", etc.)
7. It is now possible to use COMMAND directly on subcommands:
   COMMAND INFO CONFIG|GET (The pipeline syntax was inspired from ACL, and
   can be used in functions lookupCommandBySds and lookupCommandByCString)
8. STRALGO is now a container command (has "help")

## Breaking changes:
1. Command stats now show the stats per subcommand (see (5) above)
parent 4962c552
...@@ -818,8 +818,10 @@ replica-priority 100 ...@@ -818,8 +818,10 @@ replica-priority 100
# will still work. # will still work.
# skip-sanitize-payload RESTORE dump-payload sanitation is skipped. # skip-sanitize-payload RESTORE dump-payload sanitation is skipped.
# sanitize-payload RESTORE dump-payload is sanitized (default). # sanitize-payload RESTORE dump-payload is sanitized (default).
# +<command> Allow the execution of that command # +<command> Allow the execution of that command.
# -<command> Disallow the execution of that command # May be used with `|` for allowing subcommands (e.g "+config|get")
# -<command> Disallow the execution of that command.
# May be used with `|` for blocking subcommands (e.g "-config|set")
# +@<category> Allow the execution of all the commands in such category # +@<category> Allow the execution of all the commands in such category
# with valid categories are like @admin, @set, @sortedset, ... # with valid categories are like @admin, @set, @sortedset, ...
# and so forth, see the full list in the server.c file where # and so forth, see the full list in the server.c file where
...@@ -827,10 +829,10 @@ replica-priority 100 ...@@ -827,10 +829,10 @@ replica-priority 100
# The special category @all means all the commands, but currently # The special category @all means all the commands, but currently
# present in the server, and that will be loaded in the future # present in the server, and that will be loaded in the future
# via modules. # via modules.
# +<command>|subcommand Allow a specific subcommand of an otherwise # +<command>|first-arg Allow a specific first argument of an otherwise
# disabled command. Note that this form is not # disabled command. Note that this form is not
# allowed as negative like -DEBUG|SEGFAULT, but # allowed as negative like -SELECT|1, but
# only additive starting with "+". # only additive starting with "+".
# allcommands Alias for +@all. Note that it implies the ability to execute # allcommands Alias for +@all. Note that it implies the ability to execute
# all the future commands loaded via the modules system. # all the future commands loaded via the modules system.
# nocommands Alias for -@all. # nocommands Alias for -@all.
......
...@@ -42,4 +42,5 @@ $TCLSH tests/test_helper.tcl \ ...@@ -42,4 +42,5 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/datatype2 \ --single unit/moduleapi/datatype2 \
--single unit/moduleapi/cluster \ --single unit/moduleapi/cluster \
--single unit/moduleapi/aclcheck \ --single unit/moduleapi/aclcheck \
--single unit/moduleapi/subcommands \
"${@}" "${@}"
This diff is collapsed.
...@@ -793,7 +793,7 @@ int loadAppendOnlyFile(char *filename) { ...@@ -793,7 +793,7 @@ int loadAppendOnlyFile(char *filename) {
} }
/* Command lookup */ /* Command lookup */
cmd = lookupCommand(argv[0]->ptr); cmd = lookupCommand(argv,argc);
if (!cmd) { if (!cmd) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Unknown command '%s' reading the append only file", "Unknown command '%s' reading the append only file",
......
...@@ -573,7 +573,7 @@ void loadServerConfigFromString(char *config) { ...@@ -573,7 +573,7 @@ void loadServerConfigFromString(char *config) {
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) { } else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
/* DEAD OPTION */ /* DEAD OPTION */
} else if (!strcasecmp(argv[0],"rename-command") && argc == 3) { } else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
struct redisCommand *cmd = lookupCommand(argv[1]); struct redisCommand *cmd = lookupCommandBySds(argv[1]);
int retval; int retval;
if (!cmd) { if (!cmd) {
...@@ -2735,18 +2735,11 @@ standardConfig configs[] = { ...@@ -2735,18 +2735,11 @@ standardConfig configs[] = {
}; };
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* CONFIG command entry point * CONFIG HELP
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
void configCommand(client *c) { void configHelpCommand(client *c) {
/* Only allow CONFIG GET while loading. */ const char *help[] = {
if (server.loading && strcasecmp(c->argv[1]->ptr,"get")) {
addReplyError(c,"Only CONFIG GET is allowed during loading");
return;
}
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
"GET <pattern>", "GET <pattern>",
" Return parameters matching the glob-like <pattern> and their values.", " Return parameters matching the glob-like <pattern> and their values.",
"SET <directive> <value>", "SET <directive> <value>",
...@@ -2756,32 +2749,36 @@ void configCommand(client *c) { ...@@ -2756,32 +2749,36 @@ void configCommand(client *c) {
"REWRITE", "REWRITE",
" Rewrite the configuration file.", " Rewrite the configuration file.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
} else if (!strcasecmp(c->argv[1]->ptr,"set") && c->argc == 4) { }
configSetCommand(c);
} else if (!strcasecmp(c->argv[1]->ptr,"get") && c->argc == 3) { /*-----------------------------------------------------------------------------
configGetCommand(c); * CONFIG RESETSTAT
} else if (!strcasecmp(c->argv[1]->ptr,"resetstat") && c->argc == 2) { *----------------------------------------------------------------------------*/
resetServerStats();
resetCommandTableStats(); void configResetStatCommand(client *c) {
resetErrorTableStats(); resetServerStats();
addReply(c,shared.ok); resetCommandTableStats(server.commands);
} else if (!strcasecmp(c->argv[1]->ptr,"rewrite") && c->argc == 2) { resetErrorTableStats();
if (server.configfile == NULL) { addReply(c,shared.ok);
addReplyError(c,"The server is running without a config file"); }
return;
} /*-----------------------------------------------------------------------------
if (rewriteConfig(server.configfile, 0) == -1) { * CONFIG REWRITE
serverLog(LL_WARNING,"CONFIG REWRITE failed: %s", strerror(errno)); *----------------------------------------------------------------------------*/
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
} else { void configRewriteCommand(client *c) {
serverLog(LL_WARNING,"CONFIG REWRITE executed with success."); if (server.configfile == NULL) {
addReply(c,shared.ok); addReplyError(c,"The server is running without a config file");
}
} else {
addReplySubcommandSyntaxError(c);
return; return;
} }
if (rewriteConfig(server.configfile, 0) == -1) {
serverLog(LL_WARNING,"CONFIG REWRITE failed: %s", strerror(errno));
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
} else {
serverLog(LL_WARNING,"CONFIG REWRITE executed with success.");
addReply(c,shared.ok);
}
} }
...@@ -1873,21 +1873,6 @@ int lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *r ...@@ -1873,21 +1873,6 @@ int lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *r
return result->numkeys; return result->numkeys;
} }
/* Helper function to extract keys from memory command.
* MEMORY USAGE <key> */
int memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
UNUSED(cmd);
getKeysPrepareResult(result, 1);
if (argc >= 3 && !strcasecmp(argv[1]->ptr,"usage")) {
result->keys[0] = 2;
result->numkeys = 1;
return result->numkeys;
}
result->numkeys = 0;
return 0;
}
/* XREAD [BLOCK <milliseconds>] [COUNT <count>] [GROUP <groupname> <ttl>] /* XREAD [BLOCK <milliseconds>] [COUNT <count>] [GROUP <groupname> <ttl>]
* STREAMS key_1 key_2 ... key_N ID_1 ID_2 ... ID_N */ * STREAMS key_1 key_2 ... key_N ID_1 ID_2 ... ID_N */
int xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { int xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
......
...@@ -830,6 +830,8 @@ int64_t commandKeySpecsFlagsFromString(const char *s) { ...@@ -830,6 +830,8 @@ int64_t commandKeySpecsFlagsFromString(const char *s) {
return flags; return flags;
} }
RedisModuleCommandProxy *moduleCreateCommandProxy(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, int64_t flags, int firstkey, int lastkey, int keystep);
/* Register a new command in the Redis server, that will be handled by /* Register a new command in the Redis server, that will be handled by
* calling the function pointer 'cmdfunc' using the RedisModule calling * calling the function pointer 'cmdfunc' using the RedisModule calling
* convention. The function returns REDISMODULE_ERR if the specified command * convention. The function returns REDISMODULE_ERR if the specified command
...@@ -888,7 +890,7 @@ int64_t commandKeySpecsFlagsFromString(const char *s) { ...@@ -888,7 +890,7 @@ int64_t commandKeySpecsFlagsFromString(const char *s) {
* Normally this is used by a command that is used * Normally this is used by a command that is used
* to authenticate a client. * to authenticate a client.
* * **"may-replicate"**: This command may generate replication traffic, even * * **"may-replicate"**: This command may generate replication traffic, even
* though it's not a write command. * though it's not a write command.
* *
* The last three parameters specify which arguments of the new command are * The last three parameters specify which arguments of the new command are
* Redis keys. See https://redis.io/commands/command for more information. * Redis keys. See https://redis.io/commands/command for more information.
...@@ -917,16 +919,24 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c ...@@ -917,16 +919,24 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled) if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled)
return REDISMODULE_ERR; return REDISMODULE_ERR;
/* Check if the command name is busy. */
if (lookupCommandByCString(name) != NULL)
return REDISMODULE_ERR;
RedisModuleCommandProxy *cp = moduleCreateCommandProxy(ctx, name, cmdfunc, flags, firstkey, lastkey, keystep);
cp->rediscmd->arity = cmdfunc ? -1 : -2;
dictAdd(server.commands,sdsnew(name),cp->rediscmd);
dictAdd(server.orig_commands,sdsnew(name),cp->rediscmd);
cp->rediscmd->id = ACLGetCommandID(name); /* ID used for ACL. */
return REDISMODULE_OK;
}
RedisModuleCommandProxy *moduleCreateCommandProxy(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, int64_t flags, int firstkey, int lastkey, int keystep) {
struct redisCommand *rediscmd; struct redisCommand *rediscmd;
RedisModuleCommandProxy *cp; RedisModuleCommandProxy *cp;
sds cmdname = sdsnew(name); sds cmdname = sdsnew(name);
/* Check if the command name is busy. */
if (lookupCommand(cmdname) != NULL) {
sdsfree(cmdname);
return REDISMODULE_ERR;
}
/* Create a command "proxy", which is a structure that is referenced /* Create a command "proxy", which is a structure that is referenced
* in the command table, so that the generic command that works as * in the command table, so that the generic command that works as
* binding between modules and Redis, can know what function to call * binding between modules and Redis, can know what function to call
...@@ -940,7 +950,6 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c ...@@ -940,7 +950,6 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
cp->rediscmd = zmalloc(sizeof(*rediscmd)); cp->rediscmd = zmalloc(sizeof(*rediscmd));
cp->rediscmd->name = cmdname; cp->rediscmd->name = cmdname;
cp->rediscmd->proc = RedisModuleCommandDispatcher; cp->rediscmd->proc = RedisModuleCommandDispatcher;
cp->rediscmd->arity = -1;
cp->rediscmd->flags = flags | CMD_MODULE; cp->rediscmd->flags = flags | CMD_MODULE;
cp->rediscmd->getkeys_proc = (redisGetKeysProc*)(unsigned long)cp; cp->rediscmd->getkeys_proc = (redisGetKeysProc*)(unsigned long)cp;
cp->rediscmd->key_specs_max = STATIC_KEY_SPECS_NUM; cp->rediscmd->key_specs_max = STATIC_KEY_SPECS_NUM;
...@@ -967,12 +976,70 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c ...@@ -967,12 +976,70 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
cp->rediscmd->calls = 0; cp->rediscmd->calls = 0;
cp->rediscmd->rejected_calls = 0; cp->rediscmd->rejected_calls = 0;
cp->rediscmd->failed_calls = 0; cp->rediscmd->failed_calls = 0;
dictAdd(server.commands,sdsdup(cmdname),cp->rediscmd); return cp;
dictAdd(server.orig_commands,sdsdup(cmdname),cp->rediscmd); }
cp->rediscmd->id = ACLGetCommandID(cmdname); /* ID used for ACL. */
/* Very similar to RedisModule_CreateCommand except that it is used to create
* a subcommand, associated with another, container, command.
*
* Example: If a module has a configuration command, MODULE.CONFIG, then
* GET and SET should be individual subcommands, while MODULE.CONFIG is
* a command, but should not be registered with a valid `funcptr`:
*
* if (RedisModule_CreateCommand(ctx,"module.config",NULL,"",0,0,0) == REDISMODULE_ERR)
* return REDISMODULE_ERR;
*
* if (RedisModule_CreateSubcommand(ctx,"container.config","set",cmd_config_set,"",0,0,0) == REDISMODULE_ERR)
* return REDISMODULE_ERR;
*
* if (RedisModule_CreateSubcommand(ctx,"container.config","get",cmd_config_get,"",0,0,0) == REDISMODULE_ERR)
* return REDISMODULE_ERR;
*
*/
int RM_CreateSubcommand(RedisModuleCtx *ctx, const char *parent_name, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
if (flags == -1) return REDISMODULE_ERR;
if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled)
return REDISMODULE_ERR;
struct redisCommand *parent_cmd = lookupCommandByCString(parent_name);
if (!parent_cmd || !(parent_cmd->flags & CMD_MODULE))
return REDISMODULE_ERR;
if (parent_cmd->parent)
return REDISMODULE_ERR; /* We don't allow more than one level of subcommands */
RedisModuleCommandProxy *parent_cp = (void*)(unsigned long)parent_cmd->getkeys_proc;
if (parent_cp->module != ctx->module)
return REDISMODULE_ERR;
/* Check if the command name is busy within the parent command. */
if (parent_cmd->subcommands_dict && lookupCommandByCStringLogic(parent_cmd->subcommands_dict, name) != NULL)
return REDISMODULE_ERR;
RedisModuleCommandProxy *cp = moduleCreateCommandProxy(ctx, name, cmdfunc, flags, firstkey, lastkey, keystep);
cp->rediscmd->arity = -2;
commandAddSubcommand(parent_cmd, cp->rediscmd);
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* Return `struct RedisModule *` as `void *` to avoid exposing it outside of module.c. */
void *moduleGetHandleByName(char *modulename) {
return dictFetchValue(modules,modulename);
}
/* Returns 1 if `cmd` is a command of the module `modulename`. 0 otherwise. */
int moduleIsModuleCommand(void *module_handle, struct redisCommand *cmd) {
if (cmd->proc != RedisModuleCommandDispatcher)
return 0;
if (module_handle == NULL)
return 0;
RedisModuleCommandProxy *cp = (void*)(unsigned long)cmd->getkeys_proc;
return (cp->module == module_handle);
}
void extendKeySpecsIfNeeded(struct redisCommand *cmd) { void extendKeySpecsIfNeeded(struct redisCommand *cmd) {
/* We extend even if key_specs_num == key_specs_max because /* We extend even if key_specs_num == key_specs_max because
* this function is called prior to adding a new spec */ * this function is called prior to adding a new spec */
...@@ -1095,7 +1162,7 @@ int moduleSetCommandKeySpecFindKeys(RedisModuleCtx *ctx, const char *name, int i ...@@ -1095,7 +1162,7 @@ int moduleSetCommandKeySpecFindKeys(RedisModuleCtx *ctx, const char *name, int i
* *
* Example: * Example:
* *
* if (RedisModule_CreateCommand(ctx,"kspec.smove",kspec_legacy,"",0,0,0) == REDISMODULE_ERR) * if (RedisModule_CreateCommand(ctx,"kspec.smove",kspec_legacy,"",0,0,0) == REDISMODULE_ERR)
* return REDISMODULE_ERR; * return REDISMODULE_ERR;
* *
* if (RedisModule_AddCommandKeySpec(ctx,"kspec.smove","read write",&spec_id) == REDISMODULE_ERR) * if (RedisModule_AddCommandKeySpec(ctx,"kspec.smove","read write",&spec_id) == REDISMODULE_ERR)
...@@ -1112,6 +1179,11 @@ int moduleSetCommandKeySpecFindKeys(RedisModuleCtx *ctx, const char *name, int i ...@@ -1112,6 +1179,11 @@ int moduleSetCommandKeySpecFindKeys(RedisModuleCtx *ctx, const char *name, int i
* if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.smove",spec_id,0,1,0) == REDISMODULE_ERR) * if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.smove",spec_id,0,1,0) == REDISMODULE_ERR)
* return REDISMODULE_ERR; * return REDISMODULE_ERR;
* *
* It is also possible to use this API on subcommands (See RedisModule_CreateSubcommand).
* The name of the subcommand should be the name of the parent command + "|" + name of subcommand.
* Example:
* RedisModule_AddCommandKeySpec(ctx,"module.config|get","read",&spec_id)
*
* Returns REDISMODULE_OK on success * Returns REDISMODULE_OK on success
*/ */
int RM_AddCommandKeySpec(RedisModuleCtx *ctx, const char *name, const char *specflags, int *spec_id) { int RM_AddCommandKeySpec(RedisModuleCtx *ctx, const char *name, const char *specflags, int *spec_id) {
...@@ -4858,7 +4930,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch ...@@ -4858,7 +4930,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
/* Lookup command now, after filters had a chance to make modifications /* Lookup command now, after filters had a chance to make modifications
* if necessary. * if necessary.
*/ */
cmd = lookupCommand(c->argv[0]->ptr); cmd = lookupCommand(c->argv,c->argc);
if (!cmd) { if (!cmd) {
errno = ENOENT; errno = ENOENT;
goto cleanup; goto cleanup;
...@@ -7373,7 +7445,7 @@ int RM_ACLCheckCommandPermissions(RedisModuleUser *user, RedisModuleString **arg ...@@ -7373,7 +7445,7 @@ int RM_ACLCheckCommandPermissions(RedisModuleUser *user, RedisModuleString **arg
struct redisCommand *cmd; struct redisCommand *cmd;
/* Find command */ /* Find command */
if ((cmd = lookupCommand(argv[0]->ptr)) == NULL) { if ((cmd = lookupCommand(argv, argc)) == NULL) {
errno = ENOENT; errno = ENOENT;
return REDISMODULE_ERR; return REDISMODULE_ERR;
} }
...@@ -9745,8 +9817,7 @@ void moduleCommand(client *c) { ...@@ -9745,8 +9817,7 @@ void moduleCommand(client *c) {
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
} else } else if (!strcasecmp(subcmd,"load") && c->argc >= 3) {
if (!strcasecmp(subcmd,"load") && c->argc >= 3) {
robj **argv = NULL; robj **argv = NULL;
int argc = 0; int argc = 0;
...@@ -9964,7 +10035,7 @@ int *RM_GetCommandKeys(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, ...@@ -9964,7 +10035,7 @@ int *RM_GetCommandKeys(RedisModuleCtx *ctx, RedisModuleString **argv, int argc,
int *res = NULL; int *res = NULL;
/* Find command */ /* Find command */
if ((cmd = lookupCommand(argv[0]->ptr)) == NULL) { if ((cmd = lookupCommand(argv,argc)) == NULL) {
errno = ENOENT; errno = ENOENT;
return NULL; return NULL;
} }
...@@ -10243,6 +10314,7 @@ void moduleRegisterCoreAPI(void) { ...@@ -10243,6 +10314,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(Free); REGISTER_API(Free);
REGISTER_API(Strdup); REGISTER_API(Strdup);
REGISTER_API(CreateCommand); REGISTER_API(CreateCommand);
REGISTER_API(CreateSubcommand);
REGISTER_API(SetModuleAttribs); REGISTER_API(SetModuleAttribs);
REGISTER_API(IsModuleNameBusy); REGISTER_API(IsModuleNameBusy);
REGISTER_API(WrongArity); REGISTER_API(WrongArity);
......
...@@ -2399,7 +2399,8 @@ sds catClientInfoString(sds s, client *client) { ...@@ -2399,7 +2399,8 @@ sds catClientInfoString(sds s, client *client) {
/* Compute the total memory consumed by this client. */ /* Compute the total memory consumed by this client. */
size_t obufmem, total_mem = getClientMemoryUsage(client, &obufmem); size_t obufmem, total_mem = getClientMemoryUsage(client, &obufmem);
return sdscatfmt(s, sds cmdname = client->lastcmd ? getFullCommandName(client->lastcmd) : NULL;
sds ret = sdscatfmt(s,
"id=%U addr=%s laddr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U argv-mem=%U multi-mem=%U obl=%U oll=%U omem=%U tot-mem=%U events=%s cmd=%s user=%s redir=%I resp=%i", "id=%U addr=%s laddr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U argv-mem=%U multi-mem=%U obl=%U oll=%U omem=%U tot-mem=%U events=%s cmd=%s user=%s redir=%I resp=%i",
(unsigned long long) client->id, (unsigned long long) client->id,
getClientPeerId(client), getClientPeerId(client),
...@@ -2422,10 +2423,13 @@ sds catClientInfoString(sds s, client *client) { ...@@ -2422,10 +2423,13 @@ sds catClientInfoString(sds s, client *client) {
(unsigned long long) obufmem, /* should not include client->buf since we want to see 0 for static clients. */ (unsigned long long) obufmem, /* should not include client->buf since we want to see 0 for static clients. */
(unsigned long long) total_mem, (unsigned long long) total_mem,
events, events,
client->lastcmd ? client->lastcmd->name : "NULL", cmdname ? cmdname : "NULL",
client->user ? client->user->name : "(superuser)", client->user ? client->user->name : "(superuser)",
(client->flags & CLIENT_TRACKING) ? (long long) client->client_tracking_redirection : -1, (client->flags & CLIENT_TRACKING) ? (long long) client->client_tracking_redirection : -1,
client->resp); client->resp);
if (cmdname)
sdsfree(cmdname);
return ret;
} }
sds getAllClientsInfoString(int type) { sds getAllClientsInfoString(int type) {
...@@ -2568,6 +2572,8 @@ void clientCommand(client *c) { ...@@ -2568,6 +2572,8 @@ void clientCommand(client *c) {
" Control the replies sent to the current connection.", " Control the replies sent to the current connection.",
"SETNAME <name>", "SETNAME <name>",
" Assign the name <name> to the current connection.", " Assign the name <name> to the current connection.",
"GETNAME",
" Get the name of the current connection.",
"UNBLOCK <clientid> [TIMEOUT|ERROR]", "UNBLOCK <clientid> [TIMEOUT|ERROR]",
" Unblock the specified blocked client.", " Unblock the specified blocked client.",
"TRACKING (ON|OFF) [REDIRECT <id>] [BCAST] [PREFIX <prefix> [...]]", "TRACKING (ON|OFF) [REDIRECT <id>] [BCAST] [PREFIX <prefix> [...]]",
...@@ -2575,6 +2581,8 @@ void clientCommand(client *c) { ...@@ -2575,6 +2581,8 @@ void clientCommand(client *c) {
" Control server assisted client side caching.", " Control server assisted client side caching.",
"TRACKINGINFO", "TRACKINGINFO",
" Report tracking status for the current connection.", " Report tracking status for the current connection.",
"NO-EVICT (ON|OFF)",
" Protect current client connection from eviction.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
...@@ -2638,7 +2646,7 @@ NULL ...@@ -2638,7 +2646,7 @@ NULL
return; return;
} }
} else if (!strcasecmp(c->argv[1]->ptr,"no-evict") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"no-evict") && c->argc == 3) {
/* CLIENT PROTECT ON|OFF */ /* CLIENT NO-EVICT ON|OFF */
if (!strcasecmp(c->argv[2]->ptr,"on")) { if (!strcasecmp(c->argv[2]->ptr,"on")) {
c->flags |= CLIENT_NO_EVICT; c->flags |= CLIENT_NO_EVICT;
addReply(c,shared.ok); addReply(c,shared.ok);
...@@ -3195,7 +3203,7 @@ void replaceClientCommandVector(client *c, int argc, robj **argv) { ...@@ -3195,7 +3203,7 @@ void replaceClientCommandVector(client *c, int argc, robj **argv) {
for (j = 0; j < c->argc; j++) for (j = 0; j < c->argc; j++)
if (c->argv[j]) if (c->argv[j])
c->argv_len_sum += getStringObjectLen(c->argv[j]); c->argv_len_sum += getStringObjectLen(c->argv[j]);
c->cmd = lookupCommandOrOriginal(c->argv[0]->ptr); c->cmd = lookupCommandOrOriginal(c->argv,c->argc);
serverAssertWithInfo(c,NULL,c->cmd != NULL); serverAssertWithInfo(c,NULL,c->cmd != NULL);
} }
...@@ -3227,7 +3235,7 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) { ...@@ -3227,7 +3235,7 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
/* If this is the command name make sure to fix c->cmd. */ /* If this is the command name make sure to fix c->cmd. */
if (i == 0) { if (i == 0) {
c->cmd = lookupCommandOrOriginal(c->argv[0]->ptr); c->cmd = lookupCommandOrOriginal(c->argv,c->argc);
serverAssertWithInfo(c,NULL,c->cmd != NULL); serverAssertWithInfo(c,NULL,c->cmd != NULL);
} }
} }
......
...@@ -406,6 +406,11 @@ void punsubscribeCommand(client *c) { ...@@ -406,6 +406,11 @@ void punsubscribeCommand(client *c) {
/* PUBLISH <channel> <message> */ /* PUBLISH <channel> <message> */
void publishCommand(client *c) { void publishCommand(client *c) {
if (server.sentinel_mode) {
sentinelPublishCommand(c);
return;
}
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]); int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
if (server.cluster_enabled) if (server.cluster_enabled)
clusterPropagatePublish(c->argv[1],c->argv[2]); clusterPropagatePublish(c->argv[1],c->argv[2]);
......
...@@ -604,6 +604,7 @@ REDISMODULE_API void * (*RedisModule_Calloc)(size_t nmemb, size_t size) REDISMOD ...@@ -604,6 +604,7 @@ REDISMODULE_API void * (*RedisModule_Calloc)(size_t nmemb, size_t size) REDISMOD
REDISMODULE_API char * (*RedisModule_Strdup)(const char *str) REDISMODULE_ATTR; REDISMODULE_API char * (*RedisModule_Strdup)(const char *str) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetApi)(const char *, void *) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetApi)(const char *, void *) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_CreateSubcommand)(RedisModuleCtx *ctx, const char *parent_name, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_IsModuleNameBusy)(const char *name) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_IsModuleNameBusy)(const char *name) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_WrongArity)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_WrongArity)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
...@@ -924,6 +925,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -924,6 +925,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(Realloc); REDISMODULE_GET_API(Realloc);
REDISMODULE_GET_API(Strdup); REDISMODULE_GET_API(Strdup);
REDISMODULE_GET_API(CreateCommand); REDISMODULE_GET_API(CreateCommand);
REDISMODULE_GET_API(CreateSubcommand);
REDISMODULE_GET_API(SetModuleAttribs); REDISMODULE_GET_API(SetModuleAttribs);
REDISMODULE_GET_API(IsModuleNameBusy); REDISMODULE_GET_API(IsModuleNameBusy);
REDISMODULE_GET_API(WrongArity); REDISMODULE_GET_API(WrongArity);
......
...@@ -2812,6 +2812,11 @@ void replicaofCommand(client *c) { ...@@ -2812,6 +2812,11 @@ void replicaofCommand(client *c) {
* (master or slave) and additional information related to replication * (master or slave) and additional information related to replication
* in an easy to process format. */ * in an easy to process format. */
void roleCommand(client *c) { void roleCommand(client *c) {
if (server.sentinel_mode) {
sentinelRoleCommand(c);
return;
}
if (server.masterhost == NULL) { if (server.masterhost == NULL) {
listIter li; listIter li;
listNode *ln; listNode *ln;
......
...@@ -817,7 +817,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) { ...@@ -817,7 +817,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
} }
/* Command lookup */ /* Command lookup */
cmd = lookupCommand(argv[0]->ptr); cmd = lookupCommand(argv,argc);
if (!cmd || ((cmd->arity > 0 && cmd->arity != argc) || if (!cmd || ((cmd->arity > 0 && cmd->arity != argc) ||
(argc < -cmd->arity))) (argc < -cmd->arity)))
{ {
......
...@@ -456,32 +456,10 @@ dictType renamedCommandsDictType = { ...@@ -456,32 +456,10 @@ dictType renamedCommandsDictType = {
/* =========================== Initialization =============================== */ /* =========================== Initialization =============================== */
void sentinelCommand(client *c);
void sentinelInfoCommand(client *c);
void sentinelSetCommand(client *c); void sentinelSetCommand(client *c);
void sentinelPublishCommand(client *c);
void sentinelRoleCommand(client *c);
void sentinelConfigGetCommand(client *c); void sentinelConfigGetCommand(client *c);
void sentinelConfigSetCommand(client *c); void sentinelConfigSetCommand(client *c);
struct redisCommand sentinelcmds[] = {
{"ping",pingCommand,1,"fast @connection"},
{"sentinel",sentinelCommand,-2,"admin"},
{"subscribe",subscribeCommand,-2,"pub-sub"},
{"unsubscribe",unsubscribeCommand,-1,"pub-sub"},
{"psubscribe",psubscribeCommand,-2,"pub-sub"},
{"punsubscribe",punsubscribeCommand,-1,"pub-sub"},
{"publish",sentinelPublishCommand,3,"pub-sub fast"},
{"info",sentinelInfoCommand,-1,"random @dangerous"},
{"role",sentinelRoleCommand,1,"fast read-only @dangerous"},
{"client",clientCommand,-2,"admin random @connection"},
{"shutdown",shutdownCommand,-1,"admin"},
{"auth",authCommand,-2,"no-auth fast @connection"},
{"hello",helloCommand,-1,"no-auth fast @connection"},
{"acl",aclCommand,-2,"admin"},
{"command",commandCommand,-1, "random @connection"}
};
/* this array is used for sentinel config lookup, which need to be loaded /* this array is used for sentinel config lookup, which need to be loaded
* before monitoring masters config to avoid dependency issues */ * before monitoring masters config to avoid dependency issues */
const char *preMonitorCfgName[] = { const char *preMonitorCfgName[] = {
...@@ -507,28 +485,6 @@ void freeSentinelLoadQueueEntry(void *item); ...@@ -507,28 +485,6 @@ void freeSentinelLoadQueueEntry(void *item);
/* Perform the Sentinel mode initialization. */ /* Perform the Sentinel mode initialization. */
void initSentinel(void) { void initSentinel(void) {
unsigned int j;
/* Remove usual Redis commands from the command table, then just add
* the SENTINEL command. */
dictEmpty(server.commands,NULL);
dictEmpty(server.orig_commands,NULL);
ACLClearCommandID();
for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) {
int retval;
struct redisCommand *cmd = sentinelcmds+j;
cmd->id = ACLGetCommandID(cmd->name); /* Assign the ID used for ACL. */
retval = dictAdd(server.commands, sdsnew(cmd->name), cmd);
serverAssert(retval == DICT_OK);
retval = dictAdd(server.orig_commands, sdsnew(cmd->name), cmd);
serverAssert(retval == DICT_OK);
/* Translate the command string flags description into an actual
* set of flags. */
if (populateSingleCommand(cmd,cmd->sflags) == C_ERR)
serverPanic("Unsupported command flag");
}
/* Initialize various data structures. */ /* Initialize various data structures. */
sentinel.current_epoch = 0; sentinel.current_epoch = 0;
sentinel.masters = dictCreate(&instancesDictType); sentinel.masters = dictCreate(&instancesDictType);
......
This diff is collapsed.
...@@ -233,6 +233,9 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT]; ...@@ -233,6 +233,9 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
#define CMD_CATEGORY_TRANSACTION (1ULL<<38) #define CMD_CATEGORY_TRANSACTION (1ULL<<38)
#define CMD_CATEGORY_SCRIPTING (1ULL<<39) #define CMD_CATEGORY_SCRIPTING (1ULL<<39)
#define CMD_SENTINEL (1ULL<<40) /* "sentinel" flag */
#define CMD_ONLY_SENTINEL (1ULL<<41) /* "only-sentinel" flag */
/* AOF states */ /* AOF states */
#define AOF_OFF 0 /* AOF is off */ #define AOF_OFF 0 /* AOF is off */
#define AOF_ON 1 /* AOF is on */ #define AOF_ON 1 /* AOF is on */
...@@ -886,20 +889,29 @@ typedef struct { ...@@ -886,20 +889,29 @@ typedef struct {
uint64_t flags; /* See USER_FLAG_* */ uint64_t flags; /* See USER_FLAG_* */
/* The bit in allowed_commands is set if this user has the right to /* The bit in allowed_commands is set if this user has the right to
* execute this command. In commands having subcommands, if this bit is * execute this command.
* set, then all the subcommands are also available.
* *
* If the bit for a given command is NOT set and the command has * If the bit for a given command is NOT set and the command has
* subcommands, Redis will also check allowed_subcommands in order to * allowed first-args, Redis will also check allowed_firstargs in order to
* understand if the command can be executed. */ * understand if the command can be executed. */
uint64_t allowed_commands[USER_COMMAND_BITS_COUNT/64]; uint64_t allowed_commands[USER_COMMAND_BITS_COUNT/64];
/* This array points, for each command ID (corresponding to the command /* NOTE: allowed_firstargs is a transformation of the old mechanism for allowing
* subcommands (now, subcommands are actually commands, with their own
* ACL ID)
* We had to keep allowed_firstargs (previously called allowed_subcommands)
* in order to support the widespread abuse of ACL rules to block a command
* with a specific argv[1] (which is not a subcommand at all).
* For example, a user can use the rule "-select +select|0" to block all
* SELECT commands, except "SELECT 0".
* It can also be applied for subcommands: "+config -config|set +config|set|loglevel"
*
* This array points, for each command ID (corresponding to the command
* bit set in allowed_commands), to an array of SDS strings, terminated by * bit set in allowed_commands), to an array of SDS strings, terminated by
* a NULL pointer, with all the sub commands that can be executed for * a NULL pointer, with all the first-args that are allowed for
* this command. When no subcommands matching is used, the field is just * this command. When no first-arg matching is used, the field is just
* set to NULL to avoid allocating USER_COMMAND_BITS_COUNT pointers. */ * set to NULL to avoid allocating USER_COMMAND_BITS_COUNT pointers. */
sds **allowed_subcommands; sds **allowed_firstargs;
list *passwords; /* A list of SDS valid passwords for this user. */ list *passwords; /* A list of SDS valid passwords for this user. */
list *patterns; /* A list of allowed key patterns. If this field is NULL list *patterns; /* A list of allowed key patterns. If this field is NULL
the user cannot mention any key in a command, unless the user cannot mention any key in a command, unless
...@@ -1824,8 +1836,10 @@ struct redisCommand { ...@@ -1824,8 +1836,10 @@ struct redisCommand {
char *sflags; /* Flags as string representation, one char per flag. */ char *sflags; /* Flags as string representation, one char per flag. */
keySpec key_specs_static[STATIC_KEY_SPECS_NUM]; keySpec key_specs_static[STATIC_KEY_SPECS_NUM];
/* Use a function to determine keys arguments in a command line. /* Use a function to determine keys arguments in a command line.
* Used for Redis Cluster redirect. */ * Used for Redis Cluster redirect (may be NULL) */
redisGetKeysProc *getkeys_proc; redisGetKeysProc *getkeys_proc;
/* Array of subcommands (may be NULL) */
struct redisCommand *subcommands;
/* Runtime data */ /* Runtime data */
uint64_t flags; /* The actual flags, obtained from the 'sflags' field. */ uint64_t flags; /* The actual flags, obtained from the 'sflags' field. */
...@@ -1844,6 +1858,8 @@ struct redisCommand { ...@@ -1844,6 +1858,8 @@ struct redisCommand {
int key_specs_num; int key_specs_num;
int key_specs_max; int key_specs_max;
int movablekeys; /* See populateCommandMovableKeys */ int movablekeys; /* See populateCommandMovableKeys */
dict *subcommands_dict;
struct redisCommand *parent;
}; };
struct redisError { struct redisError {
...@@ -1980,6 +1996,8 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj ...@@ -1980,6 +1996,8 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj
int moduleDefragValue(robj *key, robj *obj, long *defragged, int dbid); int moduleDefragValue(robj *key, robj *obj, long *defragged, int dbid);
int moduleLateDefrag(robj *key, robj *value, unsigned long *cursor, long long endtime, long long *defragged, int dbid); int moduleLateDefrag(robj *key, robj *value, unsigned long *cursor, long long endtime, long long *defragged, int dbid);
long moduleDefragGlobals(void); long moduleDefragGlobals(void);
void *moduleGetHandleByName(char *modulename);
int moduleIsModuleCommand(void *module_handle, struct redisCommand *cmd);
/* Utils */ /* Utils */
long long ustime(void); long long ustime(void);
...@@ -2420,9 +2438,12 @@ void removeSignalHandlers(void); ...@@ -2420,9 +2438,12 @@ void removeSignalHandlers(void);
int createSocketAcceptHandler(socketFds *sfd, aeFileProc *accept_handler); int createSocketAcceptHandler(socketFds *sfd, aeFileProc *accept_handler);
int changeListenPort(int port, socketFds *sfd, aeFileProc *accept_handler); int changeListenPort(int port, socketFds *sfd, aeFileProc *accept_handler);
int changeBindAddr(sds *addrlist, int addrlist_len); int changeBindAddr(sds *addrlist, int addrlist_len);
struct redisCommand *lookupCommand(sds name); struct redisCommand *lookupCommand(robj **argv ,int argc);
struct redisCommand *lookupCommandBySdsLogic(dict *commands, sds s);
struct redisCommand *lookupCommandBySds(sds s);
struct redisCommand *lookupCommandByCStringLogic(dict *commands, const char *s);
struct redisCommand *lookupCommandByCString(const char *s); struct redisCommand *lookupCommandByCString(const char *s);
struct redisCommand *lookupCommandOrOriginal(sds name); struct redisCommand *lookupCommandOrOriginal(robj **argv ,int argc);
void call(client *c, int flags); void call(client *c, int flags);
void propagate(int dbid, robj **argv, int argc, int flags); void propagate(int dbid, robj **argv, int argc, int flags);
void alsoPropagate(int dbid, robj **argv, int argc, int target); void alsoPropagate(int dbid, robj **argv, int argc, int target);
...@@ -2448,7 +2469,7 @@ void usage(void); ...@@ -2448,7 +2469,7 @@ void usage(void);
void updateDictResizePolicy(void); void updateDictResizePolicy(void);
int htNeedsResize(dict *dict); int htNeedsResize(dict *dict);
void populateCommandTable(void); void populateCommandTable(void);
void resetCommandTableStats(void); void resetCommandTableStats(dict* commands);
void resetErrorTableStats(void); void resetErrorTableStats(void);
void adjustOpenFilesLimit(void); void adjustOpenFilesLimit(void);
void incrementErrorCount(const char *fullerr, size_t namelen); void incrementErrorCount(const char *fullerr, size_t namelen);
...@@ -2606,7 +2627,6 @@ int sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult * ...@@ -2606,7 +2627,6 @@ int sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *
int migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
int georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
int xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
int memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
int lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
int lmpopGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int lmpopGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
int blmpopGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int blmpopGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
...@@ -2624,6 +2644,10 @@ void queueSentinelConfig(sds *argv, int argc, int linenum, sds line); ...@@ -2624,6 +2644,10 @@ void queueSentinelConfig(sds *argv, int argc, int linenum, sds line);
void loadSentinelConfigFromQueue(void); void loadSentinelConfigFromQueue(void);
void sentinelIsRunning(void); void sentinelIsRunning(void);
void sentinelCheckConfigFile(void); void sentinelCheckConfigFile(void);
void sentinelCommand(client *c);
void sentinelInfoCommand(client *c);
void sentinelPublishCommand(client *c);
void sentinelRoleCommand(client *c);
/* redis-check-rdb & aof */ /* redis-check-rdb & aof */
int redis_check_rdb(char *rdbfilename, FILE *fp); int redis_check_rdb(char *rdbfilename, FILE *fp);
...@@ -2694,6 +2718,11 @@ void authCommand(client *c); ...@@ -2694,6 +2718,11 @@ void authCommand(client *c);
void pingCommand(client *c); void pingCommand(client *c);
void echoCommand(client *c); void echoCommand(client *c);
void commandCommand(client *c); void commandCommand(client *c);
void commandCountCommand(client *c);
void commandListCommand(client *c);
void commandInfoCommand(client *c);
void commandGetKeysCommand(client *c);
void commandHelpCommand(client *c);
void setCommand(client *c); void setCommand(client *c);
void setnxCommand(client *c); void setnxCommand(client *c);
void setexCommand(client *c); void setexCommand(client *c);
...@@ -2846,7 +2875,11 @@ void hgetallCommand(client *c); ...@@ -2846,7 +2875,11 @@ void hgetallCommand(client *c);
void hexistsCommand(client *c); void hexistsCommand(client *c);
void hscanCommand(client *c); void hscanCommand(client *c);
void hrandfieldCommand(client *c); void hrandfieldCommand(client *c);
void configCommand(client *c); void configSetCommand(client *c);
void configGetCommand(client *c);
void configResetStatCommand(client *c);
void configRewriteCommand(client *c);
void configHelpCommand(client *c);
void hincrbyCommand(client *c); void hincrbyCommand(client *c);
void hincrbyfloatCommand(client *c); void hincrbyfloatCommand(client *c);
void subscribeCommand(client *c); void subscribeCommand(client *c);
...@@ -2937,6 +2970,7 @@ void _serverPanic(const char *file, int line, const char *msg, ...); ...@@ -2937,6 +2970,7 @@ void _serverPanic(const char *file, int line, const char *msg, ...);
#endif #endif
void serverLogObjectDebugInfo(const robj *o); void serverLogObjectDebugInfo(const robj *o);
void sigsegvHandler(int sig, siginfo_t *info, void *secret); void sigsegvHandler(int sig, siginfo_t *info, void *secret);
sds getFullCommandName(struct redisCommand *cmd);
const char *getSafeInfoString(const char *s, size_t len, char **tmp); const char *getSafeInfoString(const char *s, size_t len, char **tmp);
sds genRedisInfoString(const char *section); sds genRedisInfoString(const char *section);
sds genModulesInfoString(sds info); sds genModulesInfoString(sds info);
...@@ -2948,6 +2982,7 @@ int memtest_preserving_test(unsigned long *m, size_t bytes, int passes); ...@@ -2948,6 +2982,7 @@ int memtest_preserving_test(unsigned long *m, size_t bytes, int passes);
void mixDigest(unsigned char *digest, void *ptr, size_t len); void mixDigest(unsigned char *digest, void *ptr, size_t len);
void xorDigest(unsigned char *digest, void *ptr, size_t len); void xorDigest(unsigned char *digest, void *ptr, size_t len);
int populateSingleCommand(struct redisCommand *c, char *strflags); int populateSingleCommand(struct redisCommand *c, char *strflags);
void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subcommand);
void populateCommandMovableKeys(struct redisCommand *cmd); void populateCommandMovableKeys(struct redisCommand *cmd);
void debugDelay(int usec); void debugDelay(int usec);
void killIOThreads(void); void killIOThreads(void);
......
...@@ -724,11 +724,20 @@ void strlenCommand(client *c) { ...@@ -724,11 +724,20 @@ void strlenCommand(client *c) {
* STRALGO <algorithm> ... arguments ... */ * STRALGO <algorithm> ... arguments ... */
void stralgoLCS(client *c); /* This implements the LCS algorithm. */ void stralgoLCS(client *c); /* This implements the LCS algorithm. */
void stralgoCommand(client *c) { void stralgoCommand(client *c) {
/* Select the algorithm. */ char *subcmd = c->argv[1]->ptr;
if (!strcasecmp(c->argv[1]->ptr,"lcs")) {
if (c->argc == 2 && !strcasecmp(subcmd,"help")) {
const char *help[] = {
"LCS",
" Run the longest common subsequence algorithm.",
NULL
};
addReplyHelp(c, help);
} else if (!strcasecmp(subcmd,"lcs")) {
stralgoLCS(c); stralgoLCS(c);
} else { } else {
addReplyErrorObject(c,shared.syntaxerr); addReplySubcommandSyntaxError(c);
return;
} }
} }
......
...@@ -43,7 +43,8 @@ TEST_MODULES = \ ...@@ -43,7 +43,8 @@ TEST_MODULES = \
zset.so \ zset.so \
stream.so \ stream.so \
aclcheck.so \ aclcheck.so \
list.so list.so \
subcommands.so
.PHONY: all .PHONY: all
......
#include "redismodule.h"
#define UNUSED(V) ((void) V)
int cmd_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
UNUSED(argv);
UNUSED(argc);
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK;
}
int cmd_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
UNUSED(argv);
UNUSED(argc);
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
int spec_id;
if (RedisModule_Init(ctx, "subcommands", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"subcommands.bitarray",NULL,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateSubcommand(ctx,"subcommands.bitarray","set",cmd_set,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(ctx,"subcommands.bitarray|set","write",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"subcommands.bitarray|set",spec_id,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"subcommands.bitarray|set",spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateSubcommand(ctx,"subcommands.bitarray","get",cmd_get,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(ctx,"subcommands.bitarray|get","read",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"subcommands.bitarray|get",spec_id,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"subcommands.bitarray|get",spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
/* Sanity */
RedisModule_Assert(RedisModule_CreateSubcommand(ctx,"bitarray","get",NULL,"",0,0,0) == REDISMODULE_ERR);
RedisModule_Assert(RedisModule_CreateSubcommand(ctx,"subcommands.bitarray","get",NULL,"",0,0,0) == REDISMODULE_ERR);
RedisModule_Assert(RedisModule_CreateSubcommand(ctx,"subcommands.bitarray|get","get",NULL,"",0,0,0) == REDISMODULE_ERR);
return REDISMODULE_OK;
}
...@@ -251,12 +251,71 @@ start_server {tags {"acl external:skip"}} { ...@@ -251,12 +251,71 @@ start_server {tags {"acl external:skip"}} {
test {ACLs can include single subcommands} { test {ACLs can include single subcommands} {
r ACL setuser newuser +@all -client r ACL setuser newuser +@all -client
r ACL setuser newuser +client|id +client|setname r ACL setuser newuser +client|id +client|setname
set cmdstr [dict get [r ACL getuser newuser] commands]
assert_match {+@all*-client*+client|id*} $cmdstr
assert_match {+@all*-client*+client|setname*} $cmdstr
r CLIENT ID; # Should not fail
r CLIENT SETNAME foo ; # Should not fail
catch {r CLIENT KILL type master} e
set e
} {*NOPERM*}
test {ACLs can exclude single subcommands, case 1} {
r ACL setuser newuser +@all -client|kill
set cmdstr [dict get [r ACL getuser newuser] commands]
assert_equal {+@all -client|kill} $cmdstr
r CLIENT ID; # Should not fail r CLIENT ID; # Should not fail
r CLIENT SETNAME foo ; # Should not fail r CLIENT SETNAME foo ; # Should not fail
catch {r CLIENT KILL type master} e catch {r CLIENT KILL type master} e
set e set e
} {*NOPERM*} } {*NOPERM*}
test {ACLs can exclude single subcommands, case 2} {
r ACL setuser newuser -@all +acl +config -config|set
set cmdstr [dict get [r ACL getuser newuser] commands]
assert_match {*+config*} $cmdstr
assert_match {*-config|set*} $cmdstr
r CONFIG GET loglevel; # Should not fail
catch {r CONFIG SET loglevel debug} e
set e
} {*NOPERM*}
test {ACLs can include a subcommand with a specific arg} {
r ACL setuser newuser +@all -config|get
r ACL setuser newuser +config|get|appendonly
set cmdstr [dict get [r ACL getuser newuser] commands]
assert_match {*-config|get*} $cmdstr
assert_match {*+config|get|appendonly*} $cmdstr
r CONFIG GET appendonly; # Should not fail
catch {r CONFIG GET loglevel} e
set e
} {*NOPERM*}
test {ACLs including of a type includes also subcommands} {
r ACL setuser newuser -@all +acl +@stream
r XADD key * field value
r XINFO STREAM key
}
test {ACLs can block SELECT of all but a specific DB} {
r ACL setuser newuser -@all +acl +select|0
set cmdstr [dict get [r ACL getuser newuser] commands]
assert_match {*+select|0*} $cmdstr
r SELECT 0
catch {r SELECT 1} e
set e
} {*NOPERM*}
test {ACLs can block all DEBUG subcommands except one} {
r ACL setuser newuser -@all +acl +incr +debug|object
set cmdstr [dict get [r ACL getuser newuser] commands]
assert_match {*+debug|object*} $cmdstr
r INCR key
r DEBUG OBJECT key
catch {r DEBUG SEGFAULT} e
set e
} {*NOPERM*}
test {ACLs set can include subcommands, if already full command exists} { test {ACLs set can include subcommands, if already full command exists} {
r ACL setuser bob +memory|doctor r ACL setuser bob +memory|doctor
set cmdstr [dict get [r ACL getuser bob] commands] set cmdstr [dict get [r ACL getuser bob] commands]
...@@ -272,10 +331,56 @@ start_server {tags {"acl external:skip"}} { ...@@ -272,10 +331,56 @@ start_server {tags {"acl external:skip"}} {
# Validate the new commands has got engulfed to +@all. # Validate the new commands has got engulfed to +@all.
set cmdstr [dict get [r ACL getuser bob] commands] set cmdstr [dict get [r ACL getuser bob] commands]
assert_equal {+@all} $cmdstr assert_equal {+@all} $cmdstr
r ACL setuser bob >passwd1 on
r AUTH bob passwd1
r CLIENT ID; # Should not fail r CLIENT ID; # Should not fail
r MEMORY DOCTOR; # Should not fail r MEMORY DOCTOR; # Should not fail
} }
test {ACLs set can exclude subcommands, if already full command exists} {
r ACL setuser alice +@all -memory|doctor
set cmdstr [dict get [r ACL getuser alice] commands]
assert_equal {+@all -memory|doctor} $cmdstr
r ACL setuser alice >passwd1 on
r AUTH alice passwd1
catch {r MEMORY DOCTOR} e
assert_match {*NOPERM*} $e
r MEMORY STATS ;# should work
# Validate the commands have got engulfed to -memory.
r ACL setuser alice +@all -memory
set cmdstr [dict get [r ACL getuser alice] commands]
assert_equal {+@all -memory} $cmdstr
catch {r MEMORY DOCTOR} e
assert_match {*NOPERM*} $e
catch {r MEMORY STATS} e
assert_match {*NOPERM*} $e
# Appending to the existing access string of alice.
r ACL setuser alice -@all
# Now, alice can't do anything, we need to auth newuser to execute ACL GETUSER
r AUTH newuser passwd1
# Validate the new commands has got engulfed to -@all.
set cmdstr [dict get [r ACL getuser alice] commands]
assert_equal {-@all} $cmdstr
r AUTH alice passwd1
catch {r GET key} e
assert_match {*NOPERM*} $e
catch {r MEMORY STATS} e
assert_match {*NOPERM*} $e
# Auth newuser before the next test
r AUTH newuser passwd1
}
# Note that the order of the generated ACL rules is not stable in Redis # Note that the order of the generated ACL rules is not stable in Redis
# so we need to match the different parts and not as a whole string. # so we need to match the different parts and not as a whole string.
test {ACL GETUSER is able to translate back command permissions} { test {ACL GETUSER is able to translate back command permissions} {
...@@ -459,7 +564,7 @@ start_server {tags {"acl external:skip"}} { ...@@ -459,7 +564,7 @@ start_server {tags {"acl external:skip"}} {
test {ACL HELP should not have unexpected options} { test {ACL HELP should not have unexpected options} {
catch {r ACL help xxx} e catch {r ACL help xxx} e
assert_match "*Unknown subcommand or wrong number of arguments*" $e assert_match "*wrong number of arguments*" $e
} }
test {Delete a user that the client doesn't use} { test {Delete a user that the client doesn't use} {
......
...@@ -51,7 +51,7 @@ start_server {tags {"info" "external:skip"}} { ...@@ -51,7 +51,7 @@ start_server {tags {"info" "external:skip"}} {
assert_equal [s total_error_replies] 0 assert_equal [s total_error_replies] 0
catch {r eval {redis.pcall('XGROUP', 'CREATECONSUMER', 's1', 'mygroup', 'consumer') return } 0} e catch {r eval {redis.pcall('XGROUP', 'CREATECONSUMER', 's1', 'mygroup', 'consumer') return } 0} e
assert_match {*count=1*} [errorstat ERR] assert_match {*count=1*} [errorstat ERR]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat xgroup] assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat xgroup\\|createconsumer]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat eval] assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat eval]
# EVAL command errors should still be pinpointed to him # EVAL command errors should still be pinpointed to him
...@@ -83,7 +83,7 @@ start_server {tags {"info" "external:skip"}} { ...@@ -83,7 +83,7 @@ start_server {tags {"info" "external:skip"}} {
catch {r XGROUP CREATECONSUMER mystream mygroup consumer} e catch {r XGROUP CREATECONSUMER mystream mygroup consumer} e
assert_match {NOGROUP*} $e assert_match {NOGROUP*} $e
assert_match {*count=1*} [errorstat NOGROUP] assert_match {*count=1*} [errorstat NOGROUP]
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat xgroup] assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat xgroup\\|createconsumer]
r config resetstat r config resetstat
assert_match {} [errorstat NOGROUP] assert_match {} [errorstat NOGROUP]
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment