Unverified Commit 23325c13 authored by Binbin's avatar Binbin Committed by GitHub
Browse files

sub-command support for ACL CAT and COMMAND LIST. redisCommand always stores fullname (#10127)



Summary of changes:
1. Rename `redisCommand->name` to `redisCommand->declared_name`, it is a
  const char * for native commands and SDS for module commands.
2. Store the [sub]command fullname in `redisCommand->fullname` (sds).
3. List subcommands in `ACL CAT`
4. List subcommands in `COMMAND LIST`
5. `moduleUnregisterCommands` now will also free the module subcommands.
6. RM_GetCurrentCommandName returns full command name

Other changes:
1. Add `addReplyErrorArity` and `addReplyErrorExpireTime`
2. Remove `getFullCommandName` function that now is useless.
3. Some cleanups about `fullname` since now it is SDS.
4. Delete `populateSingleCommand` function from server.h that is useless.
5. Added tests to cover this change.
6. Add some module unload tests and fix the leaks
7. Make error messages uniform, make sure they always contain the full command
  name and that it's quoted.
7. Fixes some typos

see the history in #9504, fixes #10124
Co-authored-by: default avatarOran Agra <oran@redislabs.com>
Co-authored-by: default avatarguybe7 <guy.benoish@redislabs.com>
parent a6fd2a46
...@@ -644,9 +644,7 @@ sds ACLDescribeSelectorCommandRulesSingleCommands(aclSelector *selector, aclSele ...@@ -644,9 +644,7 @@ sds ACLDescribeSelectorCommandRulesSingleCommands(aclSelector *selector, aclSele
int fakebit = ACLGetSelectorCommandBit(fake_selector,cmd->id); int fakebit = ACLGetSelectorCommandBit(fake_selector,cmd->id);
if (userbit != fakebit) { if (userbit != fakebit) {
rules = sdscatlen(rules, userbit ? "+" : "-", 1); rules = sdscatlen(rules, userbit ? "+" : "-", 1);
sds fullname = getFullCommandName(cmd); rules = sdscatsds(rules,cmd->fullname);
rules = sdscat(rules,fullname);
sdsfree(fullname);
rules = sdscatlen(rules," ",1); rules = sdscatlen(rules," ",1);
ACLChangeSelectorPerm(fake_selector,cmd,userbit); ACLChangeSelectorPerm(fake_selector,cmd,userbit);
} }
...@@ -660,9 +658,7 @@ sds ACLDescribeSelectorCommandRulesSingleCommands(aclSelector *selector, aclSele ...@@ -660,9 +658,7 @@ sds ACLDescribeSelectorCommandRulesSingleCommands(aclSelector *selector, aclSele
{ {
for (int j = 0; selector->allowed_firstargs[cmd->id][j]; j++) { for (int j = 0; selector->allowed_firstargs[cmd->id][j]; j++) {
rules = sdscatlen(rules,"+",1); rules = sdscatlen(rules,"+",1);
sds fullname = getFullCommandName(cmd); rules = sdscatsds(rules,cmd->fullname);
rules = sdscat(rules,fullname);
sdsfree(fullname);
rules = sdscatlen(rules,"|",1); rules = sdscatlen(rules,"|",1);
rules = sdscatsds(rules,selector->allowed_firstargs[cmd->id][j]); rules = sdscatsds(rules,selector->allowed_firstargs[cmd->id][j]);
rules = sdscatlen(rules," ",1); rules = sdscatlen(rules," ",1);
...@@ -994,10 +990,10 @@ cleanup: ...@@ -994,10 +990,10 @@ cleanup:
* commands. For instance ~* allows all the keys. The pattern * commands. For instance ~* allows all the keys. The pattern
* is a glob-style pattern like the one of KEYS. * is a glob-style pattern like the one of KEYS.
* It is possible to specify multiple patterns. * It is possible to specify multiple patterns.
* %R~<pattern> Add key read pattern that specifies which keys can be read * %R~<pattern> Add key read pattern that specifies which keys can be read
* from. * from.
* %W~<pattern> Add key write pattern that specifies which keys can be * %W~<pattern> Add key write pattern that specifies which keys can be
* written to. * written to.
* allkeys Alias for ~* * allkeys Alias for ~*
* resetkeys Flush the list of allowed keys patterns. * resetkeys Flush the list of allowed keys patterns.
* &<pattern> Add a pattern of channels that can be mentioned as part of * &<pattern> Add a pattern of channels that can be mentioned as part of
...@@ -1005,7 +1001,7 @@ cleanup: ...@@ -1005,7 +1001,7 @@ cleanup:
* pattern is a glob-style pattern like the one of PSUBSCRIBE. * pattern is a glob-style pattern like the one of PSUBSCRIBE.
* It is possible to specify multiple patterns. * It is possible to specify multiple patterns.
* allchannels Alias for &* * allchannels Alias for &*
* resetchannels Flush the list of allowed keys patterns. * resetchannels Flush the list of allowed channel patterns.
*/ */
int ACLSetSelector(aclSelector *selector, const char* op, size_t oplen) { int ACLSetSelector(aclSelector *selector, const char* op, size_t oplen) {
if (!strcasecmp(op,"allkeys") || if (!strcasecmp(op,"allkeys") ||
...@@ -1456,9 +1452,12 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) { ...@@ -1456,9 +1452,12 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) {
* should have an assigned ID (that is used to index the bitmap). This function * should have an assigned ID (that is used to index the bitmap). This function
* creates such an ID: it uses sequential IDs, reusing the same ID for the same * creates such an ID: it uses sequential IDs, reusing the same ID for the same
* command name, so that a command retains the same ID in case of modules that * command name, so that a command retains the same ID in case of modules that
* are unloaded and later reloaded. */ * are unloaded and later reloaded.
unsigned long ACLGetCommandID(const char *cmdname) { *
sds lowername = sdsnew(cmdname); * The function does not take ownership of the 'cmdname' SDS string.
* */
unsigned long ACLGetCommandID(sds cmdname) {
sds lowername = sdsdup(cmdname);
sdstolower(lowername); sdstolower(lowername);
if (commandId == NULL) commandId = raxNew(); if (commandId == NULL) commandId = raxNew();
void *id = raxFind(commandId,(unsigned char*)lowername,sdslen(lowername)); void *id = raxFind(commandId,(unsigned char*)lowername,sdslen(lowername));
...@@ -2374,7 +2373,7 @@ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username ...@@ -2374,7 +2373,7 @@ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username
le->object = object; le->object = object;
} else { } else {
switch(reason) { switch(reason) {
case ACL_DENIED_CMD: le->object = getFullCommandName(c->cmd); break; case ACL_DENIED_CMD: le->object = sdsdup(c->cmd->fullname); break;
case ACL_DENIED_KEY: le->object = sdsdup(c->argv[argpos]->ptr); break; case ACL_DENIED_KEY: le->object = sdsdup(c->argv[argpos]->ptr); break;
case ACL_DENIED_CHANNEL: le->object = sdsdup(c->argv[argpos]->ptr); break; case ACL_DENIED_CHANNEL: le->object = sdsdup(c->argv[argpos]->ptr); break;
case ACL_DENIED_AUTH: le->object = sdsdup(c->argv[0]->ptr); break; case ACL_DENIED_AUTH: le->object = sdsdup(c->argv[0]->ptr); break;
...@@ -2435,6 +2434,26 @@ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username ...@@ -2435,6 +2434,26 @@ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username
* ACL related commands * ACL related commands
* ==========================================================================*/ * ==========================================================================*/
/* ACL CAT category */
void aclCatWithFlags(client *c, dict *commands, uint64_t cflag, int *arraylen) {
dictEntry *de;
dictIterator *di = dictGetIterator(commands);
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
if (cmd->flags & CMD_MODULE) continue;
if (cmd->acl_categories & cflag) {
addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
(*arraylen)++;
}
if (cmd->subcommands_dict) {
aclCatWithFlags(c, cmd->subcommands_dict, cflag, arraylen);
}
}
dictReleaseIterator(di);
}
/* Add the formatted response from a single selector to the ACL GETUSER /* Add the formatted response from a single selector to the ACL GETUSER
* response. This function returns the number of fields added. * response. This function returns the number of fields added.
* *
...@@ -2686,22 +2705,12 @@ setuser_cleanup: ...@@ -2686,22 +2705,12 @@ setuser_cleanup:
} else if (!strcasecmp(sub,"cat") && c->argc == 3) { } else if (!strcasecmp(sub,"cat") && c->argc == 3) {
uint64_t cflag = ACLGetCommandCategoryFlagByName(c->argv[2]->ptr); uint64_t cflag = ACLGetCommandCategoryFlagByName(c->argv[2]->ptr);
if (cflag == 0) { if (cflag == 0) {
addReplyErrorFormat(c, "Unknown category '%s'", (char*)c->argv[2]->ptr); addReplyErrorFormat(c, "Unknown category '%.128s'", (char*)c->argv[2]->ptr);
return; return;
} }
int arraylen = 0; int arraylen = 0;
void *dl = addReplyDeferredLen(c); void *dl = addReplyDeferredLen(c);
dictIterator *di = dictGetIterator(server.orig_commands); aclCatWithFlags(c, server.orig_commands, cflag, &arraylen);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
if (cmd->flags & CMD_MODULE) continue;
if (cmd->acl_categories & cflag) {
addReplyBulkCString(c,cmd->name);
arraylen++;
}
}
dictReleaseIterator(di);
setDeferredArrayLen(c,dl,arraylen); setDeferredArrayLen(c,dl,arraylen);
} else if (!strcasecmp(sub,"genpass") && (c->argc == 2 || c->argc == 3)) { } else if (!strcasecmp(sub,"genpass") && (c->argc == 2 || c->argc == 3)) {
#define GENPASS_MAX_BITS 4096 #define GENPASS_MAX_BITS 4096
...@@ -2809,7 +2818,7 @@ setuser_cleanup: ...@@ -2809,7 +2818,7 @@ setuser_cleanup:
sds err = sdsempty(); sds err = sdsempty();
if (result == ACL_DENIED_CMD) { if (result == ACL_DENIED_CMD) {
err = sdscatfmt(err, "This user has no permissions to run " err = sdscatfmt(err, "This user has no permissions to run "
"the '%s' command or its subcommand", c->cmd->name); "the '%s' command", c->cmd->fullname);
} else if (result == ACL_DENIED_KEY) { } else if (result == ACL_DENIED_KEY) {
err = sdscatfmt(err, "This user has no permissions to access " err = sdscatfmt(err, "This user has no permissions to access "
"the '%s' key", c->argv[idx + 3]->ptr); "the '%s' key", c->argv[idx + 3]->ptr);
......
...@@ -5131,8 +5131,7 @@ NULL ...@@ -5131,8 +5131,7 @@ NULL
} else if ((!strcasecmp(c->argv[1]->ptr,"addslotsrange") || } else if ((!strcasecmp(c->argv[1]->ptr,"addslotsrange") ||
!strcasecmp(c->argv[1]->ptr,"delslotsrange")) && c->argc >= 4) { !strcasecmp(c->argv[1]->ptr,"delslotsrange")) && c->argc >= 4) {
if (c->argc % 2 == 1) { if (c->argc % 2 == 1) {
addReplyErrorFormat(c,"wrong number of arguments for '%s' command", addReplyErrorArity(c);
c->cmd->name);
return; return;
} }
/* CLUSTER ADDSLOTSRANGE <start slot> <end slot> [<start slot> <end slot> ...] */ /* CLUSTER ADDSLOTSRANGE <start slot> <end slot> [<start slot> <end slot> ...] */
......
...@@ -571,14 +571,14 @@ void expireGenericCommand(client *c, long long basetime, int unit) { ...@@ -571,14 +571,14 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
* overflow by either unit conversion or basetime addition. */ * overflow by either unit conversion or basetime addition. */
if (unit == UNIT_SECONDS) { if (unit == UNIT_SECONDS) {
if (when > LLONG_MAX / 1000 || when < LLONG_MIN / 1000) { if (when > LLONG_MAX / 1000 || when < LLONG_MIN / 1000) {
addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name); addReplyErrorExpireTime(c);
return; return;
} }
when *= 1000; when *= 1000;
} }
if (when > LLONG_MAX - basetime) { if (when > LLONG_MAX - basetime) {
addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name); addReplyErrorExpireTime(c);
return; return;
} }
when += basetime; when += basetime;
......
...@@ -522,33 +522,24 @@ void fillCommandCDF(client *c, struct hdr_histogram* histogram) { ...@@ -522,33 +522,24 @@ void fillCommandCDF(client *c, struct hdr_histogram* histogram) {
/* latencyCommand() helper to produce for all commands, /* latencyCommand() helper to produce for all commands,
* a per command cumulative distribution of latencies. */ * a per command cumulative distribution of latencies. */
void latencyAllCommandsFillCDF(client *c) { void latencyAllCommandsFillCDF(client *c, dict *commands, int *command_with_data) {
dictIterator *di = dictGetSafeIterator(server.commands); dictIterator *di = dictGetSafeIterator(commands);
dictEntry *de; dictEntry *de;
struct redisCommand *cmd; struct redisCommand *cmd;
void *replylen = addReplyDeferredLen(c);
int command_with_data = 0;
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
cmd = (struct redisCommand *) dictGetVal(de); cmd = (struct redisCommand *) dictGetVal(de);
if (cmd->latency_histogram) { if (cmd->latency_histogram) {
addReplyBulkCString(c,cmd->name); addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
fillCommandCDF(c, cmd->latency_histogram); fillCommandCDF(c, cmd->latency_histogram);
command_with_data++; (*command_with_data)++;
} }
if (cmd->subcommands) { if (cmd->subcommands) {
for (int j = 0; cmd->subcommands[j].name; j++) { latencyAllCommandsFillCDF(c, cmd->subcommands_dict, command_with_data);
struct redisCommand *sub = cmd->subcommands+j;
if (sub->latency_histogram) {
addReplyBulkSds(c,getFullCommandName(sub));
fillCommandCDF(c, sub->latency_histogram);
command_with_data++;
}
}
} }
} }
dictReleaseIterator(di); dictReleaseIterator(di);
setDeferredMapLen(c,replylen,command_with_data);
} }
/* latencyCommand() helper to produce for a specific command set, /* latencyCommand() helper to produce for a specific command set,
...@@ -564,20 +555,24 @@ void latencySpecificCommandsFillCDF(client *c) { ...@@ -564,20 +555,24 @@ void latencySpecificCommandsFillCDF(client *c) {
} }
if (cmd->latency_histogram) { if (cmd->latency_histogram) {
addReplyBulkSds(c,getFullCommandName(cmd)); addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
fillCommandCDF(c, cmd->latency_histogram); fillCommandCDF(c, cmd->latency_histogram);
command_with_data++; command_with_data++;
} }
if (cmd->subcommands) { if (cmd->subcommands_dict) {
for (int j = 0; cmd->subcommands[j].name; j++) { dictEntry *de;
struct redisCommand *sub = cmd->subcommands+j; dictIterator *di = dictGetSafeIterator(cmd->subcommands_dict);
while ((de = dictNext(di)) != NULL) {
struct redisCommand *sub = dictGetVal(de);
if (sub->latency_histogram) { if (sub->latency_histogram) {
addReplyBulkSds(c,getFullCommandName(sub)); addReplyBulkCBuffer(c, sub->fullname, sdslen(sub->fullname));
fillCommandCDF(c, sub->latency_histogram); fillCommandCDF(c, sub->latency_histogram);
command_with_data++; command_with_data++;
} }
} }
dictReleaseIterator(di);
} }
} }
setDeferredMapLen(c,replylen,command_with_data); setDeferredMapLen(c,replylen,command_with_data);
...@@ -725,7 +720,10 @@ void latencyCommand(client *c) { ...@@ -725,7 +720,10 @@ void latencyCommand(client *c) {
} else if (!strcasecmp(c->argv[1]->ptr,"histogram") && c->argc >= 2) { } else if (!strcasecmp(c->argv[1]->ptr,"histogram") && c->argc >= 2) {
/* LATENCY HISTOGRAM*/ /* LATENCY HISTOGRAM*/
if (c->argc == 2) { if (c->argc == 2) {
latencyAllCommandsFillCDF(c); int command_with_data = 0;
void *replylen = addReplyDeferredLen(c);
latencyAllCommandsFillCDF(c, server.commands, &command_with_data);
setDeferredMapLen(c, replylen, command_with_data);
} else { } else {
latencySpecificCommandsFillCDF(c); latencySpecificCommandsFillCDF(c);
} }
......
...@@ -877,7 +877,7 @@ int64_t commandKeySpecsFlagsFromString(const char *s) { ...@@ -877,7 +877,7 @@ int64_t commandKeySpecsFlagsFromString(const char *s) {
return flags; return flags;
} }
RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, const char *name, RedisModuleCmdFunc cmdfunc, int64_t flags, int firstkey, int lastkey, int keystep); RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds declared_name, sds fullname, 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
...@@ -978,19 +978,26 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c ...@@ -978,19 +978,26 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
if (lookupCommandByCString(name) != NULL) if (lookupCommandByCString(name) != NULL)
return REDISMODULE_ERR; return REDISMODULE_ERR;
RedisModuleCommand *cp = moduleCreateCommandProxy(ctx->module, name, cmdfunc, flags, firstkey, lastkey, keystep); sds declared_name = sdsnew(name);
RedisModuleCommand *cp = moduleCreateCommandProxy(ctx->module, declared_name, sdsdup(declared_name), cmdfunc, flags, firstkey, lastkey, keystep);
cp->rediscmd->arity = cmdfunc ? -1 : -2; /* Default value, can be changed later via dedicated API */ cp->rediscmd->arity = cmdfunc ? -1 : -2; /* Default value, can be changed later via dedicated API */
dictAdd(server.commands,sdsnew(name),cp->rediscmd); serverAssert(dictAdd(server.commands, sdsdup(declared_name), cp->rediscmd) == DICT_OK);
dictAdd(server.orig_commands,sdsnew(name),cp->rediscmd); serverAssert(dictAdd(server.orig_commands, sdsdup(declared_name), cp->rediscmd) == DICT_OK);
cp->rediscmd->id = ACLGetCommandID(name); /* ID used for ACL. */ cp->rediscmd->id = ACLGetCommandID(declared_name); /* ID used for ACL. */
return REDISMODULE_OK; return REDISMODULE_OK;
} }
RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, const char *name, RedisModuleCmdFunc cmdfunc, int64_t flags, int firstkey, int lastkey, int keystep) { /* A proxy that help create a module command / subcommand.
*
* 'declared_name': it contains the sub_name, which is just the fullname for non-subcommands.
* 'fullname': sds string representing the command fullname.
*
* Function will take the ownership of both 'declared_name' and 'fullname' SDS.
*/
RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds declared_name, sds fullname, RedisModuleCmdFunc cmdfunc, int64_t flags, int firstkey, int lastkey, int keystep) {
struct redisCommand *rediscmd; struct redisCommand *rediscmd;
RedisModuleCommand *cp; RedisModuleCommand *cp;
sds cmdname = sdsnew(name);
/* 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
...@@ -1003,7 +1010,8 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, const c ...@@ -1003,7 +1010,8 @@ RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, const c
cp->module = module; cp->module = module;
cp->func = cmdfunc; cp->func = cmdfunc;
cp->rediscmd = zcalloc(sizeof(*rediscmd)); cp->rediscmd = zcalloc(sizeof(*rediscmd));
cp->rediscmd->name = cmdname; cp->rediscmd->declared_name = declared_name; /* SDS for module commands */
cp->rediscmd->fullname = fullname;
cp->rediscmd->group = COMMAND_GROUP_MODULE; cp->rediscmd->group = COMMAND_GROUP_MODULE;
cp->rediscmd->proc = RedisModuleCommandDispatcher; cp->rediscmd->proc = RedisModuleCommandDispatcher;
cp->rediscmd->flags = flags | CMD_MODULE; cp->rediscmd->flags = flags | CMD_MODULE;
...@@ -1099,13 +1107,17 @@ int RM_CreateSubcommand(RedisModuleCommand *parent, const char *name, RedisModul ...@@ -1099,13 +1107,17 @@ int RM_CreateSubcommand(RedisModuleCommand *parent, const char *name, RedisModul
return REDISMODULE_ERR; /* A parent command should be a pure container of subcommands */ return REDISMODULE_ERR; /* A parent command should be a pure container of subcommands */
/* Check if the command name is busy within the parent command. */ /* Check if the command name is busy within the parent command. */
if (parent_cmd->subcommands_dict && lookupCommandByCStringLogic(parent_cmd->subcommands_dict, name) != NULL) sds declared_name = sdsnew(name);
if (parent_cmd->subcommands_dict && lookupSubcommand(parent_cmd, declared_name) != NULL) {
sdsfree(declared_name);
return REDISMODULE_ERR; return REDISMODULE_ERR;
}
RedisModuleCommand *cp = moduleCreateCommandProxy(parent->module, name, cmdfunc, flags, firstkey, lastkey, keystep); sds fullname = catSubCommandFullname(parent_cmd->fullname, name);
RedisModuleCommand *cp = moduleCreateCommandProxy(parent->module, declared_name, fullname, cmdfunc, flags, firstkey, lastkey, keystep);
cp->rediscmd->arity = -2; cp->rediscmd->arity = -2;
commandAddSubcommand(parent_cmd, cp->rediscmd); commandAddSubcommand(parent_cmd, cp->rediscmd, name);
return REDISMODULE_OK; return REDISMODULE_OK;
} }
...@@ -5056,7 +5068,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch ...@@ -5056,7 +5068,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
} }
acl_retval = ACLCheckAllUserCommandPerm(ctx->client->user,c->cmd,c->argv,c->argc,&acl_errpos); acl_retval = ACLCheckAllUserCommandPerm(ctx->client->user,c->cmd,c->argv,c->argc,&acl_errpos);
if (acl_retval != ACL_OK) { if (acl_retval != ACL_OK) {
sds object = (acl_retval == ACL_DENIED_CMD) ? sdsnew(c->cmd->name) : sdsdup(c->argv[acl_errpos]->ptr); sds object = (acl_retval == ACL_DENIED_CMD) ? sdsdup(c->cmd->fullname) : sdsdup(c->argv[acl_errpos]->ptr);
addACLLogEntry(ctx->client, acl_retval, ACL_LOG_CTX_MODULE, -1, ctx->client->user->name, object); addACLLogEntry(ctx->client, acl_retval, ACL_LOG_CTX_MODULE, -1, ctx->client->user->name, object);
errno = EACCES; errno = EACCES;
goto cleanup; goto cleanup;
...@@ -9965,40 +9977,71 @@ void moduleFreeModuleStructure(struct RedisModule *module) { ...@@ -9965,40 +9977,71 @@ void moduleFreeModuleStructure(struct RedisModule *module) {
zfree(module); zfree(module);
} }
/* Free the command registered with the specified module.
* On success C_OK is returned, otherwise C_ERR is returned.
*
* Note that caller needs to handle the deletion of the command table dict,
* and after that needs to free the command->fullname and the command itself.
*/
int moduleFreeCommand(struct RedisModule *module, struct redisCommand *cmd) {
if (cmd->proc != RedisModuleCommandDispatcher)
return C_ERR;
RedisModuleCommand *cp = (void*)(unsigned long)cmd->getkeys_proc;
if (cp->module != module)
return C_ERR;
/* Free everything except cmd->fullname and cmd itself. */
if (cmd->key_specs != cmd->key_specs_static)
zfree(cmd->key_specs);
for (int j = 0; cmd->tips && cmd->tips[j]; j++)
sdsfree((sds)cmd->tips[j]);
for (int j = 0; cmd->history && cmd->history[j].since; j++) {
sdsfree((sds)cmd->history[j].since);
sdsfree((sds)cmd->history[j].changes);
}
sdsfree((sds)cmd->summary);
sdsfree((sds)cmd->since);
sdsfree((sds)cmd->complexity);
if (cmd->latency_histogram) {
hdr_close(cmd->latency_histogram);
cmd->latency_histogram = NULL;
}
zfree(cmd->args);
zfree(cp);
if (cmd->subcommands_dict) {
dictEntry *de;
dictIterator *di = dictGetSafeIterator(cmd->subcommands_dict);
while ((de = dictNext(di)) != NULL) {
struct redisCommand *sub = dictGetVal(de);
if (moduleFreeCommand(module, sub) != C_OK) continue;
serverAssert(dictDelete(cmd->subcommands_dict, sub->declared_name) == DICT_OK);
sdsfree((sds)sub->declared_name);
sdsfree(sub->fullname);
zfree(sub);
}
dictReleaseIterator(di);
dictRelease(cmd->subcommands_dict);
}
return C_OK;
}
void moduleUnregisterCommands(struct RedisModule *module) { void moduleUnregisterCommands(struct RedisModule *module) {
/* Unregister all the commands registered by this module. */ /* Unregister all the commands registered by this module. */
dictIterator *di = dictGetSafeIterator(server.commands); dictIterator *di = dictGetSafeIterator(server.commands);
dictEntry *de; dictEntry *de;
while ((de = dictNext(di)) != NULL) { while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de); struct redisCommand *cmd = dictGetVal(de);
if (cmd->proc == RedisModuleCommandDispatcher) { if (moduleFreeCommand(module, cmd) != C_OK) continue;
RedisModuleCommand *cp =
(void*)(unsigned long)cmd->getkeys_proc; serverAssert(dictDelete(server.commands, cmd->fullname) == DICT_OK);
sds cmdname = (sds)cmd->name; serverAssert(dictDelete(server.orig_commands, cmd->fullname) == DICT_OK);
if (cp->module == module) { sdsfree((sds)cmd->declared_name);
if (cmd->key_specs != cmd->key_specs_static) sdsfree(cmd->fullname);
zfree(cmd->key_specs); zfree(cmd);
for (int j = 0; cmd->tips && cmd->tips[j]; j++)
sdsfree((sds)cmd->tips[j]);
for (int j = 0; cmd->history && cmd->history[j].since; j++) {
sdsfree((sds)cmd->history[j].since);
sdsfree((sds)cmd->history[j].changes);
}
dictDelete(server.commands,cmdname);
dictDelete(server.orig_commands,cmdname);
sdsfree(cmdname);
sdsfree((sds)cmd->summary);
sdsfree((sds)cmd->since);
sdsfree((sds)cmd->complexity);
if (cmd->latency_histogram) {
hdr_close(cmd->latency_histogram);
cmd->latency_histogram = NULL;
}
zfree(cmd->args);
zfree(cmd);
zfree(cp);
}
}
} }
dictReleaseIterator(di); dictReleaseIterator(di);
} }
...@@ -10523,7 +10566,7 @@ const char *RM_GetCurrentCommandName(RedisModuleCtx *ctx) { ...@@ -10523,7 +10566,7 @@ const char *RM_GetCurrentCommandName(RedisModuleCtx *ctx) {
if (!ctx || !ctx->client || !ctx->client->cmd) if (!ctx || !ctx->client || !ctx->client->cmd)
return NULL; return NULL;
return (const char*)ctx->client->cmd->name; return (const char*)ctx->client->cmd->fullname;
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
......
...@@ -362,10 +362,9 @@ void _addReplyToBufferOrList(client *c, const char *s, size_t len) { ...@@ -362,10 +362,9 @@ void _addReplyToBufferOrList(client *c, const char *s, size_t len) {
* Note this is the simplest way to check a command added a response. Replication links are used to write data but * Note this is the simplest way to check a command added a response. Replication links are used to write data but
* not for responses, so we should normally never get here on a replica client. */ * not for responses, so we should normally never get here on a replica client. */
if (getClientType(c) == CLIENT_TYPE_SLAVE) { if (getClientType(c) == CLIENT_TYPE_SLAVE) {
sds cmdname = c->lastcmd ? getFullCommandName(c->lastcmd) : NULL; sds cmdname = c->lastcmd ? c->lastcmd->fullname : NULL;
logInvalidUseAndFreeClientAsync(c, "Replica generated a reply to command %s", logInvalidUseAndFreeClientAsync(c, "Replica generated a reply to command '%s'",
cmdname ? cmdname : "<unknown>"); cmdname ? cmdname : "<unknown>");
sdsfree(cmdname);
return; return;
} }
...@@ -484,10 +483,10 @@ void afterErrorReply(client *c, const char *s, size_t len) { ...@@ -484,10 +483,10 @@ void afterErrorReply(client *c, const char *s, size_t len) {
} }
if (len > 4096) len = 4096; if (len > 4096) len = 4096;
const char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>"; sds cmdname = c->lastcmd ? c->lastcmd->fullname : NULL;
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error " serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
"to its %s: '%.*s' after processing the command " "to its %s: '%.*s' after processing the command "
"'%s'", from, to, (int)len, s, cmdname); "'%s'", from, to, (int)len, s, cmdname ? cmdname : "<unknown>");
if (ctype == CLIENT_TYPE_MASTER && server.repl_backlog && if (ctype == CLIENT_TYPE_MASTER && server.repl_backlog &&
server.repl_backlog->histlen > 0) server.repl_backlog->histlen > 0)
{ {
...@@ -550,6 +549,16 @@ void addReplyErrorFormat(client *c, const char *fmt, ...) { ...@@ -550,6 +549,16 @@ void addReplyErrorFormat(client *c, const char *fmt, ...) {
sdsfree(s); sdsfree(s);
} }
void addReplyErrorArity(client *c) {
addReplyErrorFormat(c, "wrong number of arguments for '%s' command",
c->cmd->fullname);
}
void addReplyErrorExpireTime(client *c) {
addReplyErrorFormat(c, "invalid expire time in '%s' command",
c->cmd->fullname);
}
void addReplyStatusLength(client *c, const char *s, size_t len) { void addReplyStatusLength(client *c, const char *s, size_t len) {
addReplyProto(c,"+",1); addReplyProto(c,"+",1);
addReplyProto(c,s,len); addReplyProto(c,s,len);
...@@ -610,10 +619,9 @@ void *addReplyDeferredLen(client *c) { ...@@ -610,10 +619,9 @@ void *addReplyDeferredLen(client *c) {
* Note this is the simplest way to check a command added a response. Replication links are used to write data but * Note this is the simplest way to check a command added a response. Replication links are used to write data but
* not for responses, so we should normally never get here on a replica client. */ * not for responses, so we should normally never get here on a replica client. */
if (getClientType(c) == CLIENT_TYPE_SLAVE) { if (getClientType(c) == CLIENT_TYPE_SLAVE) {
sds cmdname = c->lastcmd ? getFullCommandName(c->lastcmd) : NULL; sds cmdname = c->lastcmd ? c->lastcmd->fullname : NULL;
logInvalidUseAndFreeClientAsync(c, "Replica generated a reply to command %s", logInvalidUseAndFreeClientAsync(c, "Replica generated a reply to command '%s'",
cmdname ? cmdname : "<unknown>"); cmdname ? cmdname : "<unknown>");
sdsfree(cmdname);
return NULL; return NULL;
} }
...@@ -2547,7 +2555,6 @@ sds catClientInfoString(sds s, client *client) { ...@@ -2547,7 +2555,6 @@ sds catClientInfoString(sds s, client *client) {
used_blocks_of_repl_buf = last->id - cur->id + 1; used_blocks_of_repl_buf = last->id - cur->id + 1;
} }
sds cmdname = client->lastcmd ? getFullCommandName(client->lastcmd) : NULL;
sds ret = sdscatfmt(s, 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,
...@@ -2571,12 +2578,10 @@ sds catClientInfoString(sds s, client *client) { ...@@ -2571,12 +2578,10 @@ 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,
cmdname ? cmdname : "NULL", client->lastcmd ? client->lastcmd->fullname : "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; return ret;
} }
......
...@@ -4058,8 +4058,7 @@ NULL ...@@ -4058,8 +4058,7 @@ NULL
return; return;
numargserr: numargserr:
addReplyErrorFormat(c,"Wrong number of arguments for 'sentinel %s'", addReplyErrorArity(c);
(char*)c->argv[1]->ptr);
} }
#define info_section_from_redis(section_name) do { \ #define info_section_from_redis(section_name) do { \
......
...@@ -2556,7 +2556,7 @@ void InitServerLast() { ...@@ -2556,7 +2556,7 @@ void InitServerLast() {
* *
* If this functions fails it means that the legacy (first,last,step) * If this functions fails it means that the legacy (first,last,step)
* spec used by COMMAND will show 0,0,0. This is not a dire situation * spec used by COMMAND will show 0,0,0. This is not a dire situation
* because anyway the legacy (first,last,step) spec is to be dperecated * because anyway the legacy (first,last,step) spec is to be deprecated
* and one should use the new key specs scheme. * and one should use the new key specs scheme.
*/ */
void populateCommandLegacyRangeSpec(struct redisCommand *c) { void populateCommandLegacyRangeSpec(struct redisCommand *c) {
...@@ -2607,21 +2607,23 @@ void populateCommandLegacyRangeSpec(struct redisCommand *c) { ...@@ -2607,21 +2607,23 @@ void populateCommandLegacyRangeSpec(struct redisCommand *c) {
c->legacy_range_key_spec.fk.range.limit = 0; c->legacy_range_key_spec.fk.range.limit = 0;
} }
void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subcommand) { sds catSubCommandFullname(const char *parent_name, const char *sub_name) {
return sdscatfmt(sdsempty(), "%s|%s", parent_name, sub_name);
}
void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subcommand, const char *declared_name) {
if (!parent->subcommands_dict) if (!parent->subcommands_dict)
parent->subcommands_dict = dictCreate(&commandTableDictType); parent->subcommands_dict = dictCreate(&commandTableDictType);
subcommand->parent = parent; /* Assign the parent command */ subcommand->parent = parent; /* Assign the parent command */
sds fullname = getFullCommandName(subcommand); subcommand->id = ACLGetCommandID(subcommand->fullname); /* Assign the ID used for ACL. */
subcommand->id = ACLGetCommandID(fullname); /* Assign the ID used for ACL. */
sdsfree(fullname);
serverAssert(dictAdd(parent->subcommands_dict, sdsnew(subcommand->name), subcommand) == DICT_OK); serverAssert(dictAdd(parent->subcommands_dict, sdsnew(declared_name), subcommand) == DICT_OK);
} }
/* Set implicit ACl categories (see comment above the definition of /* Set implicit ACl categories (see comment above the definition of
* struct redisCommand). */ * struct redisCommand). */
void setImplictACLCategories(struct redisCommand *c) { void setImplicitACLCategories(struct redisCommand *c) {
if (c->flags & CMD_WRITE) if (c->flags & CMD_WRITE)
c->acl_categories |= ACL_CATEGORY_WRITE; c->acl_categories |= ACL_CATEGORY_WRITE;
if (c->flags & CMD_READONLY) if (c->flags & CMD_READONLY)
...@@ -2653,7 +2655,7 @@ int populateArgsStructure(struct redisCommandArg *args) { ...@@ -2653,7 +2655,7 @@ int populateArgsStructure(struct redisCommandArg *args) {
return count; return count;
} }
/* Recursively populate the command stracture. */ /* Recursively populate the command structure. */
void populateCommandStructure(struct redisCommand *c) { void populateCommandStructure(struct redisCommand *c) {
/* Redis commands don't need more args than STATIC_KEY_SPECS_NUM (Number of keys /* Redis commands don't need more args than STATIC_KEY_SPECS_NUM (Number of keys
* specs can be greater than STATIC_KEY_SPECS_NUM only for module commands) */ * specs can be greater than STATIC_KEY_SPECS_NUM only for module commands) */
...@@ -2683,38 +2685,39 @@ void populateCommandStructure(struct redisCommand *c) { ...@@ -2683,38 +2685,39 @@ void populateCommandStructure(struct redisCommand *c) {
populateCommandMovableKeys(c); populateCommandMovableKeys(c);
/* Assign the ID used for ACL. */ /* Assign the ID used for ACL. */
c->id = ACLGetCommandID(c->name); c->id = ACLGetCommandID(c->fullname);
/* Handle subcommands */ /* Handle subcommands */
if (c->subcommands) { if (c->subcommands) {
for (int j = 0; c->subcommands[j].name; j++) { for (int j = 0; c->subcommands[j].declared_name; j++) {
struct redisCommand *sub = c->subcommands+j; struct redisCommand *sub = c->subcommands+j;
/* Translate the command string flags description into an actual /* Translate the command string flags description into an actual
* set of flags. */ * set of flags. */
setImplictACLCategories(sub); setImplicitACLCategories(sub);
sub->fullname = catSubCommandFullname(c->declared_name, sub->declared_name);
populateCommandStructure(sub); populateCommandStructure(sub);
commandAddSubcommand(c,sub); commandAddSubcommand(c, sub, sub->declared_name);
} }
} }
} }
extern struct redisCommand redisCommandTable[]; extern struct redisCommand redisCommandTable[];
/* Populates the Redis Command Table starting from the hard coded list /* Populates the Redis Command Table dict from from the static table in commands.c
* we have on top of server.c file. */ * which is auto generated from the json files in the commands folder. */
void populateCommandTable(void) { void populateCommandTable(void) {
int j; int j;
struct redisCommand *c; struct redisCommand *c;
for (j = 0;; j++) { for (j = 0;; j++) {
c = redisCommandTable + j; c = redisCommandTable + j;
if (c->name == NULL) if (c->declared_name == NULL)
break; break;
int retval1, retval2; int retval1, retval2;
setImplictACLCategories(c); setImplicitACLCategories(c);
if (!(c->flags & CMD_SENTINEL) && server.sentinel_mode) if (!(c->flags & CMD_SENTINEL) && server.sentinel_mode)
continue; continue;
...@@ -2722,12 +2725,13 @@ void populateCommandTable(void) { ...@@ -2722,12 +2725,13 @@ void populateCommandTable(void) {
if (c->flags & CMD_ONLY_SENTINEL && !server.sentinel_mode) if (c->flags & CMD_ONLY_SENTINEL && !server.sentinel_mode)
continue; continue;
c->fullname = sdsnew(c->declared_name);
populateCommandStructure(c); populateCommandStructure(c);
retval1 = dictAdd(server.commands, sdsnew(c->name), c); retval1 = dictAdd(server.commands, sdsdup(c->fullname), c);
/* Populate an additional dictionary that will be unaffected /* Populate an additional dictionary that will be unaffected
* by rename-command statements in redis.conf. */ * by rename-command statements in redis.conf. */
retval2 = dictAdd(server.orig_commands, sdsnew(c->name), c); retval2 = dictAdd(server.orig_commands, sdsdup(c->fullname), c);
serverAssert(retval1 == DICT_OK && retval2 == DICT_OK); serverAssert(retval1 == DICT_OK && retval2 == DICT_OK);
} }
} }
...@@ -2752,7 +2756,6 @@ void resetCommandTableStats(dict* commands) { ...@@ -2752,7 +2756,6 @@ void resetCommandTableStats(dict* commands) {
resetCommandTableStats(c->subcommands_dict); resetCommandTableStats(c->subcommands_dict);
} }
dictReleaseIterator(di); dictReleaseIterator(di);
} }
void resetErrorTableStats(void) { void resetErrorTableStats(void) {
...@@ -2812,6 +2815,10 @@ int isContainerCommandBySds(sds s) { ...@@ -2812,6 +2815,10 @@ int isContainerCommandBySds(sds s) {
return has_subcommands; return has_subcommands;
} }
struct redisCommand *lookupSubcommand(struct redisCommand *container, sds sub_name) {
return dictFetchValue(container->subcommands_dict, sub_name);
}
/* Look up a command by argv and argc /* Look up a command by argv and argc
* *
* If `strict` is not 0 we expect argc to be exact (i.e. argc==2 * If `strict` is not 0 we expect argc to be exact (i.e. argc==2
...@@ -2832,7 +2839,7 @@ struct redisCommand *lookupCommandLogic(dict *commands, robj **argv, int argc, i ...@@ -2832,7 +2839,7 @@ struct redisCommand *lookupCommandLogic(dict *commands, robj **argv, int argc, i
if (strict && argc != 2) if (strict && argc != 2)
return NULL; return NULL;
/* Note: Currently we support just one level of subcommands */ /* Note: Currently we support just one level of subcommands */
return dictFetchValue(base_cmd->subcommands_dict, argv[1]->ptr); return lookupSubcommand(base_cmd, argv[1]->ptr);
} }
} }
...@@ -3420,8 +3427,7 @@ int processCommand(client *c) { ...@@ -3420,8 +3427,7 @@ int processCommand(client *c) {
} else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) || } else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
(c->argc < -c->cmd->arity)) (c->argc < -c->cmd->arity))
{ {
rejectCommandFormat(c,"wrong number of arguments for '%s' command or subcommand", rejectCommandFormat(c,"wrong number of arguments for '%s' command", c->cmd->fullname);
c->cmd->name);
return C_OK; return C_OK;
} }
...@@ -3478,11 +3484,9 @@ int processCommand(client *c) { ...@@ -3478,11 +3484,9 @@ int processCommand(client *c) {
switch (acl_retval) { switch (acl_retval) {
case ACL_DENIED_CMD: case ACL_DENIED_CMD:
{ {
sds cmdname = getFullCommandName(c->cmd);
rejectCommandFormat(c, rejectCommandFormat(c,
"-NOPERM this user has no permissions to run " "-NOPERM this user has no permissions to run "
"the '%s' command", cmdname); "the '%s' command", c->cmd->fullname);
sdsfree(cmdname);
break; break;
} }
case ACL_DENIED_KEY: case ACL_DENIED_KEY:
...@@ -3645,7 +3649,7 @@ int processCommand(client *c) { ...@@ -3645,7 +3649,7 @@ int processCommand(client *c) {
rejectCommandFormat(c, rejectCommandFormat(c,
"Can't execute '%s': only (P|S)SUBSCRIBE / " "Can't execute '%s': only (P|S)SUBSCRIBE / "
"(P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context", "(P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context",
c->cmd->name); c->cmd->fullname);
return C_OK; return C_OK;
} }
...@@ -4045,8 +4049,7 @@ int writeCommandsDeniedByDiskError(void) { ...@@ -4045,8 +4049,7 @@ int writeCommandsDeniedByDiskError(void) {
void pingCommand(client *c) { void pingCommand(client *c) {
/* The command takes zero or one arguments. */ /* The command takes zero or one arguments. */
if (c->argc > 2) { if (c->argc > 2) {
addReplyErrorFormat(c,"wrong number of arguments for '%s' command", addReplyErrorArity(c);
c->cmd->name);
return; return;
} }
...@@ -4376,7 +4379,7 @@ void addReplyCommandSubCommands(client *c, struct redisCommand *cmd, void (*repl ...@@ -4376,7 +4379,7 @@ void addReplyCommandSubCommands(client *c, struct redisCommand *cmd, void (*repl
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
struct redisCommand *sub = (struct redisCommand *)dictGetVal(de); struct redisCommand *sub = (struct redisCommand *)dictGetVal(de);
if (use_map) if (use_map)
addReplyBulkSds(c, getFullCommandName(sub)); addReplyBulkCBuffer(c, sub->fullname, sdslen(sub->fullname));
reply_function(c, sub); reply_function(c, sub);
} }
dictReleaseIterator(di); dictReleaseIterator(di);
...@@ -4419,10 +4422,7 @@ void addReplyCommandInfo(client *c, struct redisCommand *cmd) { ...@@ -4419,10 +4422,7 @@ void addReplyCommandInfo(client *c, struct redisCommand *cmd) {
} }
addReplyArrayLen(c, 10); addReplyArrayLen(c, 10);
if (cmd->parent) addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
addReplyBulkSds(c, getFullCommandName(cmd));
else
addReplyBulkCString(c, cmd->name);
addReplyLongLong(c, cmd->arity); addReplyLongLong(c, cmd->arity);
addReplyFlagsForCommand(c, cmd); addReplyFlagsForCommand(c, cmd);
addReplyLongLong(c, firstkey); addReplyLongLong(c, firstkey);
...@@ -4489,7 +4489,7 @@ void addReplyCommandDocs(client *c, struct redisCommand *cmd) { ...@@ -4489,7 +4489,7 @@ void addReplyCommandDocs(client *c, struct redisCommand *cmd) {
/* Helper for COMMAND(S) command /* Helper for COMMAND(S) command
* *
* COMMAND(S) GETKEYS arg0 arg1 arg2 ... */ * COMMAND(S) GETKEYS cmd arg1 arg2 ... */
void getKeysSubcommand(client *c) { void getKeysSubcommand(client *c) {
struct redisCommand *cmd = lookupCommand(c->argv+2,c->argc-2); struct redisCommand *cmd = lookupCommand(c->argv+2,c->argc-2);
getKeysResult result = GETKEYS_RESULT_INIT; getKeysResult result = GETKEYS_RESULT_INIT;
...@@ -4577,12 +4577,48 @@ int shouldFilterFromCommandList(struct redisCommand *cmd, commandListFilter *fil ...@@ -4577,12 +4577,48 @@ int shouldFilterFromCommandList(struct redisCommand *cmd, commandListFilter *fil
break; break;
} }
case (COMMAND_LIST_FILTER_PATTERN): case (COMMAND_LIST_FILTER_PATTERN):
return !stringmatchlen(filter->arg, sdslen(filter->arg), cmd->name, strlen(cmd->name), 1); return !stringmatchlen(filter->arg, sdslen(filter->arg), cmd->fullname, sdslen(cmd->fullname), 1);
default: default:
serverPanic("Invalid filter type %d", filter->type); serverPanic("Invalid filter type %d", filter->type);
} }
} }
/* COMMAND LIST FILTERBY (MODULE <module-name>|ACLCAT <cat>|PATTERN <pattern>) */
void commandListWithFilter(client *c, dict *commands, commandListFilter filter, int *numcmds) {
dictEntry *de;
dictIterator *di = dictGetIterator(commands);
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
if (!shouldFilterFromCommandList(cmd,&filter)) {
addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
(*numcmds)++;
}
if (cmd->subcommands_dict) {
commandListWithFilter(c, cmd->subcommands_dict, filter, numcmds);
}
}
dictReleaseIterator(di);
}
/* COMMAND LIST */
void commandListWithoutFilter(client *c, dict *commands, int *numcmds) {
dictEntry *de;
dictIterator *di = dictGetIterator(commands);
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
(*numcmds)++;
if (cmd->subcommands_dict) {
commandListWithoutFilter(c, cmd->subcommands_dict, numcmds);
}
}
dictReleaseIterator(di);
}
/* COMMAND LIST [FILTERBY (MODULE <module-name>|ACLCAT <cat>|PATTERN <pattern>)] */ /* COMMAND LIST [FILTERBY (MODULE <module-name>|ACLCAT <cat>|PATTERN <pattern>)] */
void commandListCommand(client *c) { void commandListCommand(client *c) {
...@@ -4613,29 +4649,16 @@ void commandListCommand(client *c) { ...@@ -4613,29 +4649,16 @@ void commandListCommand(client *c) {
} }
} }
dictIterator *di; int numcmds = 0;
dictEntry *de; void *replylen = addReplyDeferredLen(c);
di = dictGetIterator(server.commands); if (got_filter) {
if (!got_filter) { commandListWithFilter(c, server.commands, filter, &numcmds);
addReplySetLen(c, dictSize(server.commands));
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
addReplyBulkCString(c,cmd->name);
}
} else { } else {
int numcmds = 0; commandListWithoutFilter(c, server.commands, &numcmds);
void *replylen = addReplyDeferredLen(c);
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
if (!shouldFilterFromCommandList(cmd,&filter)) {
addReplyBulkCString(c,cmd->name);
numcmds++;
}
}
setDeferredArrayLen(c,replylen,numcmds);
} }
dictReleaseIterator(di);
setDeferredArrayLen(c,replylen,numcmds);
} }
/* COMMAND INFO [<command-name> ...] */ /* COMMAND INFO [<command-name> ...] */
...@@ -4670,7 +4693,7 @@ void commandDocsCommand(client *c) { ...@@ -4670,7 +4693,7 @@ void commandDocsCommand(client *c) {
di = dictGetIterator(server.commands); di = dictGetIterator(server.commands);
while ((de = dictNext(di)) != NULL) { while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de); struct redisCommand *cmd = dictGetVal(de);
addReplyBulkCString(c, cmd->name); addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
addReplyCommandDocs(c, cmd); addReplyCommandDocs(c, cmd);
} }
dictReleaseIterator(di); dictReleaseIterator(di);
...@@ -4682,10 +4705,7 @@ void commandDocsCommand(client *c) { ...@@ -4682,10 +4705,7 @@ void commandDocsCommand(client *c) {
struct redisCommand *cmd = lookupCommandBySds(c->argv[i]->ptr); struct redisCommand *cmd = lookupCommandBySds(c->argv[i]->ptr);
if (!cmd) if (!cmd)
continue; continue;
if (cmd->parent) addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname));
addReplyBulkSds(c, getFullCommandName(cmd));
else
addReplyBulkCString(c, cmd->name);
addReplyCommandDocs(c, cmd); addReplyCommandDocs(c, cmd);
numcmds++; numcmds++;
} }
...@@ -4768,14 +4788,6 @@ sds fillPercentileDistributionLatencies(sds info, const char* histogram_name, st ...@@ -4768,14 +4788,6 @@ sds fillPercentileDistributionLatencies(sds info, const char* histogram_name, st
return info; return info;
} }
sds getFullCommandName(struct redisCommand *cmd) {
if (!cmd->parent) {
return sdsnew(cmd->name);
} else {
return sdscatfmt(sdsempty(),"%s|%s",cmd->parent->name,cmd->name);
}
}
const char *replstateToString(int replstate) { const char *replstateToString(int replstate) {
switch (replstate) { switch (replstate) {
case SLAVE_STATE_WAIT_BGSAVE_START: case SLAVE_STATE_WAIT_BGSAVE_START:
...@@ -4818,16 +4830,13 @@ sds genRedisInfoStringCommandStats(sds info, dict *commands) { ...@@ -4818,16 +4830,13 @@ sds genRedisInfoStringCommandStats(sds info, dict *commands) {
char *tmpsafe; char *tmpsafe;
c = (struct redisCommand *) dictGetVal(de); c = (struct redisCommand *) dictGetVal(de);
if (c->calls || c->failed_calls || c->rejected_calls) { if (c->calls || c->failed_calls || c->rejected_calls) {
sds cmdnamesds = getFullCommandName(c);
info = sdscatprintf(info, info = sdscatprintf(info,
"cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f" "cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f"
",rejected_calls=%lld,failed_calls=%lld\r\n", ",rejected_calls=%lld,failed_calls=%lld\r\n",
getSafeInfoString(cmdnamesds, sdslen(cmdnamesds), &tmpsafe), c->calls, c->microseconds, getSafeInfoString(c->fullname, sdslen(c->fullname), &tmpsafe), c->calls, c->microseconds,
(c->calls == 0) ? 0 : ((float)c->microseconds/c->calls), (c->calls == 0) ? 0 : ((float)c->microseconds/c->calls),
c->rejected_calls, c->failed_calls); c->rejected_calls, c->failed_calls);
if (tmpsafe != NULL) zfree(tmpsafe); if (tmpsafe != NULL) zfree(tmpsafe);
sdsfree(cmdnamesds);
} }
if (c->subcommands_dict) { if (c->subcommands_dict) {
info = genRedisInfoStringCommandStats(info, c->subcommands_dict); info = genRedisInfoStringCommandStats(info, c->subcommands_dict);
...@@ -4847,13 +4856,10 @@ sds genRedisInfoStringLatencyStats(sds info, dict *commands) { ...@@ -4847,13 +4856,10 @@ sds genRedisInfoStringLatencyStats(sds info, dict *commands) {
char *tmpsafe; char *tmpsafe;
c = (struct redisCommand *) dictGetVal(de); c = (struct redisCommand *) dictGetVal(de);
if (c->latency_histogram) { if (c->latency_histogram) {
sds cmdnamesds = getFullCommandName(c);
info = fillPercentileDistributionLatencies(info, info = fillPercentileDistributionLatencies(info,
getSafeInfoString(cmdnamesds, sdslen(cmdnamesds), &tmpsafe), getSafeInfoString(c->fullname, sdslen(c->fullname), &tmpsafe),
c->latency_histogram); c->latency_histogram);
if (tmpsafe != NULL) zfree(tmpsafe); if (tmpsafe != NULL) zfree(tmpsafe);
sdsfree(cmdnamesds);
} }
if (c->subcommands_dict) { if (c->subcommands_dict) {
info = genRedisInfoStringLatencyStats(info, c->subcommands_dict); info = genRedisInfoStringLatencyStats(info, c->subcommands_dict);
......
...@@ -2165,7 +2165,8 @@ typedef int redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, ge ...@@ -2165,7 +2165,8 @@ typedef int redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, ge
*/ */
struct redisCommand { struct redisCommand {
/* Declarative data */ /* Declarative data */
const char *name; /* A string representing the command name. */ const char *declared_name; /* A string representing the command declared_name.
* It is a const char * for native commands and SDS for module commands. */
const char *summary; /* Summary of the command (optional). */ const char *summary; /* Summary of the command (optional). */
const char *complexity; /* Complexity description (optional). */ const char *complexity; /* Complexity description (optional). */
const char *since; /* Debut version of the command (optional). */ const char *since; /* Debut version of the command (optional). */
...@@ -2196,6 +2197,7 @@ struct redisCommand { ...@@ -2196,6 +2197,7 @@ struct redisCommand {
ACLs. A connection is able to execute a given command if ACLs. A connection is able to execute a given command if
the user associated to the connection has this command the user associated to the connection has this command
bit set in the bitmap of allowed commands. */ bit set in the bitmap of allowed commands. */
sds fullname; /* A SDS string representing the command fullname. */
struct hdr_histogram* latency_histogram; /*points to the command latency command histogram (unit of time nanosecond) */ struct hdr_histogram* latency_histogram; /*points to the command latency command histogram (unit of time nanosecond) */
keySpec *key_specs; keySpec *key_specs;
keySpec legacy_range_key_spec; /* The legacy (first,last,step) key spec is keySpec legacy_range_key_spec; /* The legacy (first,last,step) key spec is
...@@ -2207,7 +2209,8 @@ struct redisCommand { ...@@ -2207,7 +2209,8 @@ struct redisCommand {
int num_tips; int num_tips;
int key_specs_num; int key_specs_num;
int key_specs_max; int key_specs_max;
dict *subcommands_dict; dict *subcommands_dict; /* A dictionary that holds the subcommands, the key is the subcommand sds name
* (not the fullname), and the value is the redisCommand structure pointer. */
struct redisCommand *parent; struct redisCommand *parent;
}; };
...@@ -2403,6 +2406,8 @@ void addReplyErrorObject(client *c, robj *err); ...@@ -2403,6 +2406,8 @@ void addReplyErrorObject(client *c, robj *err);
void addReplyOrErrorObject(client *c, robj *reply); void addReplyOrErrorObject(client *c, robj *reply);
void addReplyErrorSds(client *c, sds err); void addReplyErrorSds(client *c, sds err);
void addReplyError(client *c, const char *err); void addReplyError(client *c, const char *err);
void addReplyErrorArity(client *c);
void addReplyErrorExpireTime(client *c);
void addReplyStatus(client *c, const char *status); void addReplyStatus(client *c, const char *status);
void addReplyDouble(client *c, double d); void addReplyDouble(client *c, double d);
void addReplyLongLongWithPrefix(client *c, long long ll, char prefix); void addReplyLongLongWithPrefix(client *c, long long ll, char prefix);
...@@ -2705,7 +2710,7 @@ void ACLInit(void); ...@@ -2705,7 +2710,7 @@ void ACLInit(void);
int ACLCheckUserCredentials(robj *username, robj *password); int ACLCheckUserCredentials(robj *username, robj *password);
int ACLAuthenticateUser(client *c, robj *username, robj *password); int ACLAuthenticateUser(client *c, robj *username, robj *password);
unsigned long ACLGetCommandID(const char *cmdname); unsigned long ACLGetCommandID(sds cmdname);
void ACLClearCommandID(void); void ACLClearCommandID(void);
user *ACLGetUserByName(const char *name, size_t namelen); user *ACLGetUserByName(const char *name, size_t namelen);
int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags); int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags);
...@@ -2800,12 +2805,13 @@ void removeSignalHandlers(void); ...@@ -2800,12 +2805,13 @@ 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(void); int changeBindAddr(void);
struct redisCommand *lookupCommand(robj **argv ,int argc); struct redisCommand *lookupSubcommand(struct redisCommand *container, sds sub_name);
struct redisCommand *lookupCommand(robj **argv, int argc);
struct redisCommand *lookupCommandBySdsLogic(dict *commands, sds s); struct redisCommand *lookupCommandBySdsLogic(dict *commands, sds s);
struct redisCommand *lookupCommandBySds(sds s); struct redisCommand *lookupCommandBySds(sds s);
struct redisCommand *lookupCommandByCStringLogic(dict *commands, const char *s); struct redisCommand *lookupCommandByCStringLogic(dict *commands, const char *s);
struct redisCommand *lookupCommandByCString(const char *s); struct redisCommand *lookupCommandByCString(const char *s);
struct redisCommand *lookupCommandOrOriginal(robj **argv ,int argc); struct redisCommand *lookupCommandOrOriginal(robj **argv, int argc);
void call(client *c, int flags); void call(client *c, int flags);
void alsoPropagate(int dbid, robj **argv, int argc, int target); void alsoPropagate(int dbid, robj **argv, int argc, int target);
void propagatePendingCommands(); void propagatePendingCommands();
...@@ -3379,7 +3385,6 @@ void _serverPanic(const char *file, int line, const char *msg, ...); ...@@ -3379,7 +3385,6 @@ 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);
...@@ -3389,8 +3394,8 @@ void serverLogHexDump(int level, char *descr, void *value, size_t len); ...@@ -3389,8 +3394,8 @@ void serverLogHexDump(int level, char *descr, void *value, size_t len);
int memtest_preserving_test(unsigned long *m, size_t bytes, int passes); int memtest_preserving_test(unsigned long *m, size_t bytes, int passes);
void mixDigest(unsigned char *digest, const void *ptr, size_t len); void mixDigest(unsigned char *digest, const void *ptr, size_t len);
void xorDigest(unsigned char *digest, const void *ptr, size_t len); void xorDigest(unsigned char *digest, const void *ptr, size_t len);
int populateSingleCommand(struct redisCommand *c, char *strflags); sds catSubCommandFullname(const char *parent_name, const char *sub_name);
void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subcommand); void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subcommand, const char *declared_name);
void populateCommandMovableKeys(struct redisCommand *cmd); void populateCommandMovableKeys(struct redisCommand *cmd);
void debugDelay(int usec); void debugDelay(int usec);
void killIOThreads(void); void killIOThreads(void);
......
...@@ -609,7 +609,7 @@ void hsetCommand(client *c) { ...@@ -609,7 +609,7 @@ void hsetCommand(client *c) {
robj *o; robj *o;
if ((c->argc % 2) == 1) { if ((c->argc % 2) == 1) {
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",c->cmd->name); addReplyErrorArity(c);
return; return;
} }
......
...@@ -492,8 +492,7 @@ void popGenericCommand(client *c, int where) { ...@@ -492,8 +492,7 @@ void popGenericCommand(client *c, int where) {
robj *value; robj *value;
if (c->argc > 3) { if (c->argc > 3) {
addReplyErrorFormat(c,"wrong number of arguments for '%s' command", addReplyErrorArity(c);
c->cmd->name);
return; return;
} else if (hascount) { } else if (hascount) {
/* Parse the optional count argument. */ /* Parse the optional count argument. */
......
...@@ -1810,7 +1810,7 @@ void xaddCommand(client *c) { ...@@ -1810,7 +1810,7 @@ void xaddCommand(client *c) {
/* Check arity. */ /* Check arity. */
if ((c->argc - field_pos) < 2 || ((c->argc-field_pos) % 2) == 1) { if ((c->argc - field_pos) < 2 || ((c->argc-field_pos) % 2) == 1) {
addReplyError(c,"wrong number of arguments for XADD"); addReplyErrorArity(c);
return; return;
} }
......
...@@ -160,7 +160,7 @@ static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int ...@@ -160,7 +160,7 @@ static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int
if (*milliseconds <= 0 || (unit == UNIT_SECONDS && *milliseconds > LLONG_MAX / 1000)) { if (*milliseconds <= 0 || (unit == UNIT_SECONDS && *milliseconds > LLONG_MAX / 1000)) {
/* Negative value provided or multiplication is gonna overflow. */ /* Negative value provided or multiplication is gonna overflow. */
addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name); addReplyErrorExpireTime(c);
return C_ERR; return C_ERR;
} }
...@@ -172,7 +172,7 @@ static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int ...@@ -172,7 +172,7 @@ static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int
if (*milliseconds <= 0) { if (*milliseconds <= 0) {
/* Overflow detected. */ /* Overflow detected. */
addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name); addReplyErrorExpireTime(c);
return C_ERR; return C_ERR;
} }
...@@ -557,8 +557,7 @@ void msetGenericCommand(client *c, int nx) { ...@@ -557,8 +557,7 @@ void msetGenericCommand(client *c, int nx) {
int j; int j;
if ((c->argc % 2) == 0) { if ((c->argc % 2) == 0) {
addReplyErrorFormat(c,"wrong number of arguments for '%s' command", addReplyErrorArity(c);
c->cmd->name);
return; return;
} }
......
...@@ -2538,7 +2538,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in ...@@ -2538,7 +2538,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
if (setnum < 1) { if (setnum < 1) {
addReplyErrorFormat(c, addReplyErrorFormat(c,
"at least 1 input key is needed for %s", c->cmd->name); "at least 1 input key is needed for '%s' command", c->cmd->fullname);
return; return;
} }
......
#include "redismodule.h" #include "redismodule.h"
#define UNUSED(V) ((void) V)
// A simple global user // A simple global user
static RedisModuleUser *global = NULL; static RedisModuleUser *global = NULL;
static long long client_change_delta = 0; static long long client_change_delta = 0;
...@@ -87,3 +89,12 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -87,3 +89,12 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int RedisModule_OnUnload(RedisModuleCtx *ctx) {
UNUSED(ctx);
if (global)
RedisModule_FreeModuleUser(global);
return REDISMODULE_OK;
}
...@@ -41,6 +41,8 @@ ...@@ -41,6 +41,8 @@
#include <pthread.h> #include <pthread.h>
#include <errno.h> #include <errno.h>
#define UNUSED(V) ((void) V)
RedisModuleCtx *detached_ctx = NULL; RedisModuleCtx *detached_ctx = NULL;
static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
...@@ -390,3 +392,12 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -390,3 +392,12 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int RedisModule_OnUnload(RedisModuleCtx *ctx) {
UNUSED(ctx);
if (detached_ctx)
RedisModule_FreeThreadSafeContext(detached_ctx);
return REDISMODULE_OK;
}
...@@ -16,6 +16,15 @@ int cmd_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { ...@@ -16,6 +16,15 @@ int cmd_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int cmd_get_fullname(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
UNUSED(argv);
UNUSED(argc);
const char *command_name = RedisModule_GetCurrentCommandName(ctx);
RedisModule_ReplyWithSimpleString(ctx, command_name);
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc); REDISMODULE_NOT_USED(argc);
...@@ -51,6 +60,18 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -51,6 +60,18 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_SetCommandKeySpecFindKeysRange(subcmd,spec_id,0,1,0) == REDISMODULE_ERR) if (RedisModule_SetCommandKeySpecFindKeysRange(subcmd,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
/* Get the name of the command currently running. */
if (RedisModule_CreateCommand(ctx,"subcommands.parent_get_fullname",cmd_get_fullname,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
/* Get the name of the subcommand currently running. */
if (RedisModule_CreateCommand(ctx,"subcommands.sub",NULL,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
RedisModuleCommand *fullname_parent = RedisModule_GetCommand(ctx,"subcommands.sub");
if (RedisModule_CreateSubcommand(fullname_parent,"get_fullname",cmd_get_fullname,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
/* Sanity */ /* Sanity */
/* Trying to create the same subcommand fails */ /* Trying to create the same subcommand fails */
......
...@@ -40,7 +40,7 @@ test "Sentinel Set with other error situations" { ...@@ -40,7 +40,7 @@ test "Sentinel Set with other error situations" {
assert_error "ERR Notification script seems non existing*" {S 0 SENTINEL SET mymaster notification-script test.txt} assert_error "ERR Notification script seems non existing*" {S 0 SENTINEL SET mymaster notification-script test.txt}
# wrong parameter number # wrong parameter number
assert_error "ERR wrong number of arguments for 'set' command or subcommand" {S 0 SENTINEL SET mymaster fakeoption} assert_error "ERR wrong number of arguments for 'sentinel|set' command" {S 0 SENTINEL SET mymaster fakeoption}
# unknown parameter option # unknown parameter option
assert_error "ERR Unknown option or number of arguments for SENTINEL SET 'fakeoption'" {S 0 SENTINEL SET mymaster fakeoption fakevalue} assert_error "ERR Unknown option or number of arguments for SENTINEL SET 'fakeoption'" {S 0 SENTINEL SET mymaster fakeoption fakevalue}
......
...@@ -40,6 +40,12 @@ proc assert_failed {expected_err detail} { ...@@ -40,6 +40,12 @@ proc assert_failed {expected_err detail} {
error "assertion:$expected_err $detail" error "assertion:$expected_err $detail"
} }
proc assert_not_equal {value expected {detail ""}} {
if {!($expected ne $value)} {
assert_failed "Expected '$value' not equal to '$expected'" $detail
}
}
proc assert_equal {value expected {detail ""}} { proc assert_equal {value expected {detail ""}} {
if {$expected ne $value} { if {$expected ne $value} {
assert_failed "Expected '$value' to be equal to '$expected'" $detail assert_failed "Expected '$value' to be equal to '$expected'" $detail
......
...@@ -476,6 +476,26 @@ start_server {tags {"acl external:skip"}} { ...@@ -476,6 +476,26 @@ start_server {tags {"acl external:skip"}} {
} }
} }
test "ACL CAT with illegal arguments" {
assert_error {*Unknown category 'NON_EXISTS'} {r ACL CAT NON_EXISTS}
assert_error {*Unknown subcommand or wrong number of arguments for 'CAT'*} {r ACL CAT NON_EXISTS NON_EXISTS2}
}
test "ACL CAT without category - list all categories" {
set categories [r acl cat]
assert_not_equal [lsearch $categories "keyspace"] -1
assert_not_equal [lsearch $categories "connection"] -1
}
test "ACL CAT category - list all commands/subcommands that belong to category" {
assert_not_equal [lsearch [r acl cat transaction] "multi"] -1
assert_not_equal [lsearch [r acl cat scripting] "function|list"] -1
# Negative check to make sure it doesn't actually return all commands.
assert_equal [lsearch [r acl cat keyspace] "set"] -1
assert_equal [lsearch [r acl cat stream] "get"] -1
}
test {ACL #5998 regression: memory leaks adding / removing subcommands} { test {ACL #5998 regression: memory leaks adding / removing subcommands} {
r AUTH default "" r AUTH default ""
r ACL setuser newuser reset -debug +debug|a +debug|b +debug|c r ACL setuser newuser reset -debug +debug|a +debug|b +debug|c
...@@ -632,7 +652,7 @@ start_server {tags {"acl external:skip"}} { ...@@ -632,7 +652,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 "*wrong number of arguments*" $e assert_match "*wrong number of arguments for 'acl|help' command" $e
} }
test {Delete a user that the client doesn't use} { test {Delete a user that the client doesn't use} {
......
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