summaryrefslogtreecommitdiff
path: root/src/server.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/server.c')
-rw-r--r--src/server.c1029
1 files changed, 887 insertions, 142 deletions
diff --git a/src/server.c b/src/server.c
index 78ef2925f..17672fbd6 100644
--- a/src/server.c
+++ b/src/server.c
@@ -171,6 +171,10 @@ struct redisServer server; /* Server global state */
* or may just execute read commands. A command can not be marked
* both "write" and "may-replicate"
*
+ * sentinel: This command is present in sentinel mode too.
+ *
+ * sentinel-only: This command is present only when in sentinel mode.
+ *
* The following additional flags are only used in order to put commands
* in a specific ACL category. Commands can have multiple ACL categories.
* See redis.conf for the exact meaning of each.
@@ -193,11 +197,499 @@ struct redisServer server; /* Server global state */
* TYPE, EXPIRE*, PEXPIRE*, TTL, PTTL, ...
*/
+struct redisCommand configSubcommands[] = {
+ {"set",configSetCommand,4,
+ "admin ok-stale no-script"},
-struct redisCommand redisCommandTable[] = {
- {"module",moduleCommand,-2,
+ {"get",configGetCommand,3,
+ "admin ok-loading ok-stale no-script"},
+
+ {"resetstat",configResetStatCommand,2,
+ "admin ok-stale no-script"},
+
+ {"rewrite",configRewriteCommand,2,
+ "admin ok-stale no-script"},
+
+ {"help",configHelpCommand,2,
+ "ok-stale ok-loading"},
+
+ {NULL},
+};
+
+struct redisCommand xinfoSubcommands[] = {
+ {"consumers",xinfoCommand,4,
+ "read-only random @stream",
+ {{"read",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"groups",xinfoCommand,3,
+ "read-only @stream",
+ {{"read",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"stream",xinfoCommand,-3,
+ "read-only @stream",
+ {{"read",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"help",xinfoCommand,2,
+ "ok-stale ok-loading @stream"},
+
+ {NULL},
+};
+
+struct redisCommand xgroupSubcommands[] = {
+ {"create",xgroupCommand,-5,
+ "write use-memory @stream",
+ {{"write",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"setid",xgroupCommand,5,
+ "write @stream",
+ {{"write",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"destroy",xgroupCommand,4,
+ "write @stream",
+ {{"write",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"createconsumer",xgroupCommand,5,
+ "write use-memory @stream",
+ {{"write",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"delconsumer",xgroupCommand,5,
+ "write @stream",
+ {{"write",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"help",xgroupCommand,2,
+ "ok-stale ok-loading @stream"},
+
+ {NULL},
+};
+
+struct redisCommand commandSubcommands[] = {
+ {"count",commandCountCommand,2,
+ "ok-loading ok-stale @connection"},
+
+ {"list",commandListCommand,-2,
+ "ok-loading ok-stale @connection"},
+
+ {"info",commandInfoCommand,-3,
+ "ok-loading ok-stale @connection"},
+
+ {"getkeys",commandGetKeysCommand,-4,
+ "ok-loading ok-stale @connection"},
+
+ {"help",commandHelpCommand,2,
+ "ok-loading ok-stale @connection"},
+
+ {NULL},
+};
+
+struct redisCommand memorySubcommands[] = {
+ {"doctor",memoryCommand,2,
+ "random"},
+
+ {"stats",memoryCommand,2,
+ "random"},
+
+ {"malloc-stats",memoryCommand,2,
+ "random"},
+
+ {"purge",memoryCommand,2,
+ ""},
+
+ {"usage",memoryCommand,-3,
+ "read-only",
+ {{"read",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"help",memoryCommand,2,
+ "ok-stale ok-loading"},
+
+ {NULL},
+};
+
+struct redisCommand aclSubcommands[] = {
+ {"cat",aclCommand,-2,
+ "no-script ok-loading ok-stale sentinel"},
+
+ {"deluser",aclCommand,-3,
+ "admin no-script ok-loading ok-stale sentinel"},
+
+ {"genpass",aclCommand,-2,
+ "no-script ok-loading ok-stale sentinel"},
+
+ {"getuser",aclCommand,3,
+ "admin no-script ok-loading ok-stale sentinel"},
+
+ {"list",aclCommand,2,
+ "admin no-script ok-loading ok-stale sentinel"},
+
+ {"load",aclCommand,2,
+ "admin no-script ok-loading ok-stale sentinel"},
+
+ {"log",aclCommand,-2,
+ "admin no-script ok-loading ok-stale sentinel"},
+
+ {"save",aclCommand,2,
+ "admin no-script ok-loading ok-stale sentinel"},
+
+ {"setuser",aclCommand,-3,
+ "admin no-script ok-loading ok-stale sentinel"},
+
+ {"users",aclCommand,2,
+ "admin no-script ok-loading ok-stale sentinel"},
+
+ {"whoami",aclCommand,2,
+ "no-script ok-loading ok-stale sentinel"},
+
+ {"help",aclCommand,2,
+ "ok-stale ok-loading sentinel"},
+
+ {NULL},
+};
+
+struct redisCommand latencySubcommands[] = {
+ {"doctor",latencyCommand,2,
+ "admin no-script ok-loading ok-stale"},
+
+ {"graph",latencyCommand,3,
+ "admin no-script ok-loading ok-stale"},
+
+ {"history",latencyCommand,3,
+ "admin no-script ok-loading ok-stale"},
+
+ {"latest",latencyCommand,2,
+ "admin no-script ok-loading ok-stale"},
+
+ {"reset",latencyCommand,-2,
+ "admin no-script ok-loading ok-stale"},
+
+ {"help",latencyCommand,2,
+ "ok-stale ok-loading"},
+
+ {NULL},
+};
+
+struct redisCommand moduleSubcommands[] = {
+ {"list",moduleCommand,2,
"admin no-script"},
+ {"load",moduleCommand,-3,
+ "admin no-script"},
+
+ {"unload",moduleCommand,3,
+ "admin no-script"},
+
+ {"help",moduleCommand,2,
+ "ok-stale ok-loading"},
+
+ {NULL},
+};
+
+struct redisCommand slowlogSubcommands[] = {
+ {"get",slowlogCommand,-2,
+ "admin random ok-loading ok-stale"},
+
+ {"len",slowlogCommand,2,
+ "admin random ok-loading ok-stale"},
+
+ {"reset",slowlogCommand,2,
+ "admin ok-loading ok-stale"},
+
+ {"help",slowlogCommand,2,
+ "ok-stale ok-loading"},
+
+ {NULL},
+};
+
+struct redisCommand objectSubcommands[] = {
+ {"encoding",objectCommand,3,
+ "read-only @keyspace",
+ {{"read",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"freq",objectCommand,3,
+ "read-only random @keyspace",
+ {{"read",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"idletime",objectCommand,3,
+ "read-only random @keyspace",
+ {{"read",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"refcount",objectCommand,3,
+ "read-only @keyspace",
+ {{"read",
+ KSPEC_BS_INDEX,.bs.index={2},
+ KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+
+ {"help",objectCommand,2,
+ "ok-stale ok-loading @keyspace"},
+
+ {NULL},
+};
+
+struct redisCommand scriptSubcommands[] = {
+ {"debug",scriptCommand,3,
+ "no-script @scripting"},
+
+ {"exists",scriptCommand,-3,
+ "no-script @scripting"},
+
+ {"flush",scriptCommand,-2,
+ "may-replicate no-script @scripting"},
+
+ {"kill",scriptCommand,2,
+ "no-script @scripting"},
+
+ {"load",scriptCommand,3,
+ "may-replicate no-script @scripting"},
+
+ {"help",scriptCommand,2,
+ "ok-loading ok-stale @scripting"},
+
+ {NULL},
+};
+
+struct redisCommand clientSubcommands[] = {
+ {"caching",clientCommand,3,
+ "no-script ok-loading ok-stale @connection"},
+
+ {"getredir",clientCommand,2,
+ "no-script ok-loading ok-stale @connection"},
+
+ {"id",clientCommand,2,
+ "no-script ok-loading ok-stale @connection"},
+
+ {"info",clientCommand,2,
+ "no-script random ok-loading ok-stale @connection"},
+
+ {"kill",clientCommand,-3,
+ "admin no-script ok-loading ok-stale @connection"},
+
+ {"list",clientCommand,-2,
+ "admin no-script random ok-loading ok-stale @connection"},
+
+ {"unpause",clientCommand,2,
+ "admin no-script ok-loading ok-stale @connection"},
+
+ {"pause",clientCommand,-3,
+ "admin no-script ok-loading ok-stale @connection"},
+
+ {"reply",clientCommand,3,
+ "no-script ok-loading ok-stale @connection"},
+
+ {"setname",clientCommand,3,
+ "no-script ok-loading ok-stale @connection"},
+
+ {"getname",clientCommand,2,
+ "no-script ok-loading ok-stale @connection"},
+
+ {"unblock",clientCommand,-3,
+ "admin no-script ok-loading ok-stale @connection"},
+
+ {"tracking",clientCommand,-3,
+ "no-script ok-loading ok-stale @connection"},
+
+ {"trackinginfo",clientCommand,2,
+ "admin no-script ok-loading ok-stale @connection"},
+
+ {"no-evict",clientCommand,3,
+ "admin no-script ok-loading ok-stale @connection"},
+
+ {"help",clientCommand,2,
+ "ok-loading ok-stale @connection"},
+
+ {NULL},
+};
+
+struct redisCommand stralgoSubcommands[] = {
+ {"lcs",stralgoCommand,-5,
+ "read-only @string",
+ {{"read incomplete", /* We can't use "keyword" here because we may give false information. */
+ KSPEC_BS_UNKNOWN,{{0}},
+ KSPEC_FK_UNKNOWN,{{0}}}},
+ lcsGetKeys},
+
+ {"help",stralgoCommand,2,
+ "ok-loading ok-stale @string"},
+
+ {NULL},
+};
+
+struct redisCommand pubsubSubcommands[] = {
+ {"channels",pubsubCommand,-2,
+ "pub-sub ok-loading ok-stale"},
+
+ {"numpat",pubsubCommand,2,
+ "pub-sub ok-loading ok-stale"},
+
+ {"numsub",pubsubCommand,-2,
+ "pub-sub ok-loading ok-stale"},
+
+ {"help",pubsubCommand,2,
+ "ok-loading ok-stale"},
+
+ {NULL},
+};
+
+struct redisCommand clusterSubcommands[] = {
+ {"addslots",clusterCommand,-3,
+ "admin ok-stale random"},
+
+ {"bumpepoch",clusterCommand,2,
+ "admin ok-stale random"},
+
+ {"count-failure-reports",clusterCommand,3,
+ "admin ok-stale random"},
+
+ {"countkeysinslots",clusterCommand,3,
+ "ok-stale random"},
+
+ {"delslots",clusterCommand,-3,
+ "admin ok-stale random"},
+
+ {"failover",clusterCommand,-2,
+ "admin ok-stale random"},
+
+ {"forget",clusterCommand,3,
+ "admin ok-stale random"},
+
+ {"getkeysinslot",clusterCommand,4,
+ "ok-stale random"},
+
+ {"flushslots",clusterCommand,2,
+ "admin ok-stale random"},
+
+ {"info",clusterCommand,2,
+ "ok-stale random"},
+
+ {"keyslot",clusterCommand,3,
+ "ok-stale random"},
+
+ {"meet",clusterCommand,-4,
+ "admin ok-stale random"},
+
+ {"myid",clusterCommand,2,
+ "ok-stale random"},
+
+ {"nodes",clusterCommand,2,
+ "ok-stale random"},
+
+ {"replicate",clusterCommand,3,
+ "admin ok-stale random"},
+
+ {"reset",clusterCommand,3,
+ "admin ok-stale random"},
+
+ {"set-config-epoch",clusterCommand,3,
+ "admin ok-stale random"},
+
+ {"setslot",clusterCommand,-4,
+ "admin ok-stale random"},
+
+ {"replicas",clusterCommand,3,
+ "admin ok-stale random"},
+
+ {"saveconfig",clusterCommand,2,
+ "admin ok-stale random"},
+
+ {"slots",clusterCommand,2,
+ "ok-stale random"},
+
+ {"help",clusterCommand,2,
+ "ok-loading ok-stale"},
+
+ {NULL},
+};
+
+struct redisCommand sentinelSubcommands[] = {
+ {"ckquorum",sentinelCommand,3,
+ "admin only-sentinel"},
+
+ {"config",sentinelCommand,-3,
+ "admin only-sentinel"},
+
+ {"debug",sentinelCommand,2,
+ "admin only-sentinel"},
+
+ {"get-master-addr-by-name",sentinelCommand,3,
+ "admin only-sentinel"},
+
+ {"failover",sentinelCommand,3,
+ "admin only-sentinel"},
+
+ {"flushconfig",sentinelCommand,2,
+ "admin only-sentinel"},
+
+ {"info-cache",sentinelCommand,3,
+ "admin only-sentinel"},
+
+ {"is-master-down-by-addr",sentinelCommand,6,
+ "admin only-sentinel"},
+
+ {"master",sentinelCommand,3,
+ "admin only-sentinel"},
+
+ {"masters",sentinelCommand,2,
+ "admin only-sentinel"},
+
+ {"monitor",sentinelCommand,6,
+ "admin only-sentinel"},
+
+ {"myid",sentinelCommand,2,
+ "admin only-sentinel"},
+
+ {"pending-scripts",sentinelCommand,2,
+ "admin only-sentinel"},
+
+ {"remove",sentinelCommand,3,
+ "admin only-sentinel"},
+
+ {"replicas",sentinelCommand,3,
+ "admin only-sentinel"},
+
+ {"reset",sentinelCommand,3,
+ "admin only-sentinel"},
+
+ {"sentinels",sentinelCommand,3,
+ "admin only-sentinel"},
+
+ {"set",sentinelCommand,5,
+ "admin only-sentinel"},
+
+ {"simulate-failure",sentinelCommand,3,
+ "admin only-sentinel"},
+
+ {"help",sentinelCommand,2,
+ "ok-loading ok-stale only-sentinel"},
+
+ {NULL},
+};
+
+struct redisCommand redisCommandTable[] = {
+ {"module",NULL,-2,
+ "",
+ .subcommands=moduleSubcommands},
+
{"get",getCommand,2,
"read-only fast @string",
{{"read",
@@ -1028,13 +1520,17 @@ struct redisCommand redisCommandTable[] = {
"read-only fast @keyspace"},
{"auth",authCommand,-2,
- "no-auth no-script ok-loading ok-stale fast @connection"},
+ "no-auth no-script ok-loading ok-stale fast sentinel @connection"},
/* We don't allow PING during loading since in Redis PING is used as
* failure detection, and a loading server is considered to be
* not available. */
{"ping",pingCommand,-1,
- "ok-stale fast @connection"},
+ "ok-stale fast sentinel @connection"},
+
+ {"sentinel",NULL,-2,
+ "admin only-sentinel",
+ .subcommands=sentinelSubcommands},
{"echo",echoCommand,2,
"fast @connection"},
@@ -1049,7 +1545,7 @@ struct redisCommand redisCommandTable[] = {
"admin no-script"},
{"shutdown",shutdownCommand,-1,
- "admin no-script ok-loading ok-stale"},
+ "admin no-script ok-loading ok-stale sentinel"},
{"lastsave",lastsaveCommand,1,
"random fast ok-loading ok-stale @admin @dangerous"},
@@ -1101,7 +1597,7 @@ struct redisCommand redisCommandTable[] = {
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"info",infoCommand,-1,
- "ok-loading ok-stale random @dangerous"},
+ "ok-loading ok-stale random sentinel @dangerous"},
{"monitor",monitorCommand,1,
"admin no-script ok-loading ok-stale"},
@@ -1149,31 +1645,33 @@ struct redisCommand redisCommandTable[] = {
"admin no-script ok-stale"},
{"role",roleCommand,1,
- "ok-loading ok-stale no-script fast @admin @dangerous"},
+ "ok-loading ok-stale no-script fast sentinel @admin @dangerous"},
{"debug",debugCommand,-2,
"admin no-script ok-loading ok-stale"},
- {"config",configCommand,-2,
- "admin ok-loading ok-stale no-script"},
+ {"config",NULL,-2,
+ "",
+ .subcommands=configSubcommands},
{"subscribe",subscribeCommand,-2,
- "pub-sub no-script ok-loading ok-stale"},
+ "pub-sub no-script ok-loading ok-stale sentinel"},
{"unsubscribe",unsubscribeCommand,-1,
- "pub-sub no-script ok-loading ok-stale"},
+ "pub-sub no-script ok-loading ok-stale sentinel"},
{"psubscribe",psubscribeCommand,-2,
- "pub-sub no-script ok-loading ok-stale"},
+ "pub-sub no-script ok-loading ok-stale sentinel"},
{"punsubscribe",punsubscribeCommand,-1,
- "pub-sub no-script ok-loading ok-stale"},
+ "pub-sub no-script ok-loading ok-stale sentinel"},
{"publish",publishCommand,3,
- "pub-sub ok-loading ok-stale fast may-replicate"},
+ "pub-sub ok-loading ok-stale fast may-replicate sentinel"},
- {"pubsub",pubsubCommand,-2,
- "pub-sub ok-loading ok-stale random"},
+ {"pubsub",NULL,-2,
+ "",
+ .subcommands=pubsubSubcommands},
{"watch",watchCommand,-2,
"no-script fast ok-loading ok-stale @transaction",
@@ -1184,8 +1682,9 @@ struct redisCommand redisCommandTable[] = {
{"unwatch",unwatchCommand,1,
"no-script fast ok-loading ok-stale @transaction"},
- {"cluster",clusterCommand,-2,
- "admin ok-stale random"},
+ {"cluster",NULL,-2,
+ "",
+ .subcommands=clusterSubcommands},
{"restore",restoreCommand,-4,
"write use-memory @keyspace @dangerous",
@@ -1224,24 +1723,20 @@ struct redisCommand redisCommandTable[] = {
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
- {"object",objectCommand,-2,
- "read-only random @keyspace",
- {{"read",
- KSPEC_BS_INDEX,.bs.index={2},
- KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+ {"object",NULL,-2,
+ "",
+ .subcommands=objectSubcommands},
- {"memory",memoryCommand,-2,
- "random read-only",
- {{"read",
- KSPEC_BS_KEYWORD,.bs.keyword={"USAGE",1},
- KSPEC_FK_RANGE,.fk.range={0,1,0}}},
- memoryGetKeys},
+ {"memory",NULL,-2,
+ "",
+ .subcommands=memorySubcommands},
- {"client",clientCommand,-2,
- "admin no-script random ok-loading ok-stale @connection"},
+ {"client",NULL,-2,
+ "sentinel",
+ .subcommands=clientSubcommands},
{"hello",helloCommand,-1,
- "no-auth no-script fast ok-loading ok-stale @connection"},
+ "no-auth no-script fast ok-loading ok-stale sentinel @connection"},
/* EVAL can modify the dataset, however it is not flagged as a write
* command since we do the check while running commands from Lua.
@@ -1277,11 +1772,13 @@ struct redisCommand redisCommandTable[] = {
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
evalGetKeys},
- {"slowlog",slowlogCommand,-2,
- "admin random ok-loading ok-stale"},
+ {"slowlog",NULL,-2,
+ "",
+ .subcommands=slowlogSubcommands},
- {"script",scriptCommand,-2,
- "no-script may-replicate @scripting"},
+ {"script",NULL,-2,
+ "",
+ .subcommands=scriptSubcommands},
{"time",timeCommand,1,
"random fast ok-loading ok-stale"},
@@ -1311,7 +1808,8 @@ struct redisCommand redisCommandTable[] = {
"no-script @connection"},
{"command",commandCommand,-1,
- "ok-loading ok-stale random @connection"},
+ "ok-loading ok-stale random sentinel @connection",
+ .subcommands=commandSubcommands},
{"geoadd",geoaddCommand,-5,
"write use-memory @geo",
@@ -1465,11 +1963,9 @@ struct redisCommand redisCommandTable[] = {
KSPEC_FK_RANGE,.fk.range={-1,1,2}}},
xreadGetKeys},
- {"xgroup",xgroupCommand,-2,
- "write use-memory @stream",
- {{"write",
- KSPEC_BS_INDEX,.bs.index={2},
- KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+ {"xgroup",NULL,-2,
+ "",
+ .subcommands=xgroupSubcommands},
{"xsetid",xsetidCommand,3,
"write use-memory fast @stream",
@@ -1501,11 +1997,9 @@ struct redisCommand redisCommandTable[] = {
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
- {"xinfo",xinfoCommand,-2,
- "read-only random @stream",
- {{"write",
- KSPEC_BS_INDEX,.bs.index={2},
- KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
+ {"xinfo",NULL,-2,
+ "",
+ .subcommands=xinfoSubcommands},
{"xdel",xdelCommand,-3,
"write fast @stream",
@@ -1525,21 +2019,20 @@ struct redisCommand redisCommandTable[] = {
{"host:",securityWarningCommand,-1,
"ok-loading ok-stale read-only"},
- {"latency",latencyCommand,-2,
- "admin no-script ok-loading ok-stale"},
+ {"latency",NULL,-2,
+ "",
+ .subcommands=latencySubcommands},
{"lolwut",lolwutCommand,-1,
"read-only fast"},
- {"acl",aclCommand,-2,
- "admin no-script ok-loading ok-stale"},
+ {"acl",NULL,-2,
+ "",
+ .subcommands=aclSubcommands},
- {"stralgo",stralgoCommand,-2,
- "read-only @string",
- {{"read incomplete", /* We can't use "keyword" here because we may give false information. */
- KSPEC_BS_UNKNOWN,{{0}},
- KSPEC_FK_UNKNOWN,{{0}}}},
- lcsGetKeys},
+ {"stralgo",NULL,-2,
+ "",
+ .subcommands=stralgoSubcommands},
{"reset",resetCommand,1,
"no-script ok-stale ok-loading fast @connection"},
@@ -3942,6 +4435,18 @@ void populateCommandLegacyRangeSpec(struct redisCommand *c) {
c->legacy_range_key_spec.fk.range.limit = 0;
}
+void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subcommand) {
+ if (!parent->subcommands_dict)
+ parent->subcommands_dict = dictCreate(&commandTableDictType);
+
+ subcommand->parent = parent; /* Assign the parent command */
+ sds fullname = getFullCommandName(subcommand);
+ subcommand->id = ACLGetCommandID(fullname); /* Assign the ID used for ACL. */
+ sdsfree(fullname);
+
+ serverAssert(dictAdd(parent->subcommands_dict, sdsnew(subcommand->name), subcommand) == DICT_OK);
+}
+
/* Parse the flags string description 'strflags' and set them to the
* command 'c'. If the flags are all valid C_OK is returned, otherwise
* C_ERR is returned (yet the recognized flags are set in the command). */
@@ -3987,6 +4492,10 @@ int populateSingleCommand(struct redisCommand *c, char *strflags) {
c->flags |= CMD_NO_AUTH;
} else if (!strcasecmp(flag,"may-replicate")) {
c->flags |= CMD_MAY_REPLICATE;
+ } else if (!strcasecmp(flag,"sentinel")) {
+ c->flags |= CMD_SENTINEL;
+ } else if (!strcasecmp(flag,"only-sentinel")) {
+ c->flags |= CMD_ONLY_SENTINEL;
} else {
/* Parse ACL categories here if the flag name starts with @. */
uint64_t catflag;
@@ -4041,6 +4550,20 @@ int populateSingleCommand(struct redisCommand *c, char *strflags) {
/* Handle the "movablekeys" flag (must be done after populating all key specs). */
populateCommandMovableKeys(c);
+ /* Handle subcommands */
+ if (c->subcommands) {
+ for (int j = 0; c->subcommands[j].name; j++) {
+ struct redisCommand *sub = c->subcommands+j;
+
+ /* Translate the command string flags description into an actual
+ * set of flags. */
+ if (populateSingleCommand(sub,sub->sflags) == C_ERR)
+ serverPanic("Unsupported command flag or key spec flag");
+
+ commandAddSubcommand(c,sub);
+ }
+ }
+
return C_OK;
}
@@ -4052,14 +4575,23 @@ void populateCommandTable(void) {
for (j = 0; j < numcommands; j++) {
struct redisCommand *c = redisCommandTable+j;
+
+ if (!(c->flags & CMD_SENTINEL) && server.sentinel_mode)
+ continue;
+
+ if (c->flags & CMD_ONLY_SENTINEL && !server.sentinel_mode)
+ continue;
+
int retval1, retval2;
+ /* Assign the ID used for ACL. */
+ c->id = ACLGetCommandID(c->name);
+
/* Translate the command string flags description into an actual
* set of flags. */
if (populateSingleCommand(c,c->sflags) == C_ERR)
serverPanic("Unsupported command flag or key spec flag");
- c->id = ACLGetCommandID(c->name); /* Assign the ID used for ACL. */
retval1 = dictAdd(server.commands, sdsnew(c->name), c);
/* Populate an additional dictionary that will be unaffected
* by rename-command statements in redis.conf. */
@@ -4068,18 +4600,20 @@ void populateCommandTable(void) {
}
}
-void resetCommandTableStats(void) {
+void resetCommandTableStats(dict* commands) {
struct redisCommand *c;
dictEntry *de;
dictIterator *di;
- di = dictGetSafeIterator(server.commands);
+ di = dictGetSafeIterator(commands);
while((de = dictNext(di)) != NULL) {
c = (struct redisCommand *) dictGetVal(de);
c->microseconds = 0;
c->calls = 0;
c->rejected_calls = 0;
c->failed_calls = 0;
+ if (c->subcommands_dict)
+ resetCommandTableStats(c->subcommands_dict);
}
dictReleaseIterator(di);
@@ -4127,19 +4661,62 @@ void redisOpArrayFree(redisOpArray *oa) {
/* ====================== Commands lookup and execution ===================== */
-struct redisCommand *lookupCommand(sds name) {
- return dictFetchValue(server.commands, name);
+struct redisCommand *lookupCommandLogic(dict *commands, robj **argv, int argc) {
+ struct redisCommand *base_cmd = dictFetchValue(commands, argv[0]->ptr);
+ int has_subcommands = base_cmd && base_cmd->subcommands_dict;
+ if (argc == 1 || !has_subcommands) {
+ /* Note: It is possible that base_cmd->proc==NULL (e.g. CONFIG) */
+ return base_cmd;
+ } else {
+ /* Note: Currently we support just one level of subcommands */
+ return dictFetchValue(base_cmd->subcommands_dict, argv[1]->ptr);
+ }
}
-struct redisCommand *lookupCommandByCString(const char *s) {
+struct redisCommand *lookupCommand(robj **argv, int argc) {
+ return lookupCommandLogic(server.commands,argv,argc);
+}
+
+struct redisCommand *lookupCommandBySdsLogic(dict *commands, sds s) {
+ int argc, j;
+ sds *strings = sdssplitlen(s,sdslen(s),"|",1,&argc);
+ if (strings == NULL)
+ return NULL;
+ if (argc > 2) {
+ /* Currently we support just one level of subcommands */
+ sdsfreesplitres(strings,argc);
+ return NULL;
+ }
+
+ robj objects[argc];
+ robj *argv[argc];
+ for (j = 0; j < argc; j++) {
+ initStaticStringObject(objects[j],strings[j]);
+ argv[j] = &objects[j];
+ }
+
+ struct redisCommand *cmd = lookupCommandLogic(commands,argv,argc);
+ sdsfreesplitres(strings,argc);
+ return cmd;
+}
+
+struct redisCommand *lookupCommandBySds(sds s) {
+ return lookupCommandBySdsLogic(server.commands,s);
+}
+
+struct redisCommand *lookupCommandByCStringLogic(dict *commands, const char *s) {
struct redisCommand *cmd;
sds name = sdsnew(s);
- cmd = dictFetchValue(server.commands, name);
+ cmd = lookupCommandBySdsLogic(commands,name);
sdsfree(name);
return cmd;
}
+struct redisCommand *lookupCommandByCString(const char *s) {
+ return lookupCommandByCStringLogic(server.commands,s);
+}
+
/* Lookup the command in the current table, if not found also check in
* the original table containing the original command names unaffected by
* redis.conf rename-command statement.
@@ -4147,10 +4724,10 @@ struct redisCommand *lookupCommandByCString(const char *s) {
* This is used by functions rewriting the argument vector such as
* rewriteClientCommandVector() in order to set client->cmd pointer
* correctly even if the command was renamed. */
-struct redisCommand *lookupCommandOrOriginal(sds name) {
- struct redisCommand *cmd = dictFetchValue(server.commands, name);
+struct redisCommand *lookupCommandOrOriginal(robj **argv ,int argc) {
+ struct redisCommand *cmd = lookupCommandLogic(server.commands, argv, argc);
- if (!cmd) cmd = dictFetchValue(server.orig_commands,name);
+ if (!cmd) cmd = lookupCommandLogic(server.orig_commands, argv, argc);
return cmd;
}
@@ -4608,19 +5185,26 @@ int processCommand(client *c) {
/* Now lookup the command and check ASAP about trivial error conditions
* such as wrong arity, bad command name and so forth. */
- c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
+ c->cmd = c->lastcmd = lookupCommand(c->argv,c->argc);
if (!c->cmd) {
+ if (lookupCommandBySds(c->argv[0]->ptr)) {
+ /* If we can't find the command but argv[0] by itself is a command
+ * it means we're dealing with an invalid subcommand. Print Help. */
+ addReplySubcommandSyntaxError(c);
+ return C_OK;
+ }
sds args = sdsempty();
int i;
for (i=1; i < c->argc && sdslen(args) < 128; i++)
- args = sdscatprintf(args, "`%.*s`, ", 128-(int)sdslen(args), (char*)c->argv[i]->ptr);
- rejectCommandFormat(c,"unknown command `%s`, with args beginning with: %s",
+ args = sdscatprintf(args, "'%.*s' ", 128-(int)sdslen(args), (char*)c->argv[i]->ptr);
+ rejectCommandFormat(c,"unknown command '%s', with args beginning with: %s",
(char*)c->argv[0]->ptr, args);
sdsfree(args);
return C_OK;
} else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
- (c->argc < -c->cmd->arity)) {
- rejectCommandFormat(c,"wrong number of arguments for '%s' command",
+ (c->argc < -c->cmd->arity))
+ {
+ rejectCommandFormat(c,"wrong number of arguments for '%s' command or subcommand",
c->cmd->name);
return C_OK;
}
@@ -5134,6 +5718,7 @@ void addReplyFlagsForCommand(client *c, struct redisCommand *cmd) {
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_FAST, "fast");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_NO_AUTH, "no_auth");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_MAY_REPLICATE, "may_replicate");
+ /* "sentinel" and "only-sentinel" are hidden on purpose. */
if (cmd->movablekeys) {
addReplyStatus(c, "movablekeys");
flagcount += 1;
@@ -5238,6 +5823,24 @@ void addReplyCommandKeyArgs(client *c, struct redisCommand *cmd) {
}
}
+void addReplyCommand(client *c, struct redisCommand *cmd);
+
+void addReplyCommandSubCommands(client *c, struct redisCommand *cmd) {
+ if (!cmd->subcommands_dict) {
+ addReplySetLen(c, 0);
+ return;
+ }
+
+ addReplyArrayLen(c, dictSize(cmd->subcommands_dict));
+ dictEntry *de;
+ dictIterator *di = dictGetSafeIterator(cmd->subcommands_dict);
+ while((de = dictNext(di)) != NULL) {
+ struct redisCommand *sub = (struct redisCommand *)dictGetVal(de);
+ addReplyCommand(c,sub);
+ }
+ dictReleaseIterator(di);
+}
+
/* Output the representation of a Redis command. Used by the COMMAND command. */
void addReplyCommand(client *c, struct redisCommand *cmd) {
if (!cmd) {
@@ -5251,8 +5854,8 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
lastkey += firstkey;
keystep = cmd->legacy_range_key_spec.fk.range.keystep;
}
- /* We are adding: command name, arg count, flags, first, last, offset, categories, key args */
- addReplyArrayLen(c, 8);
+ /* We are adding: command name, arg count, flags, first, last, offset, categories, key args, subcommands */
+ addReplyArrayLen(c, 9);
addReplyBulkCString(c, cmd->name);
addReplyLongLong(c, cmd->arity);
addReplyFlagsForCommand(c, cmd);
@@ -5261,70 +5864,188 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
addReplyLongLong(c, keystep);
addReplyCommandCategories(c,cmd);
addReplyCommandKeyArgs(c,cmd);
+ addReplyCommandSubCommands(c,cmd);
}
}
-/* COMMAND <subcommand> <args> */
+/* Helper for COMMAND(S) command
+ *
+ * COMMAND(S) GETKEYS arg0 arg1 arg2 ... */
+void getKeysSubcommand(client *c) {
+ struct redisCommand *cmd = lookupCommand(c->argv+2,c->argc-2);
+ getKeysResult result = GETKEYS_RESULT_INIT;
+ int j;
+
+ if (!cmd) {
+ addReplyError(c,"Invalid command specified");
+ return;
+ } else if (cmd->getkeys_proc == NULL && cmd->key_specs_num == 0) {
+ addReplyError(c,"The command has no key arguments");
+ return;
+ } else if ((cmd->arity > 0 && cmd->arity != c->argc-2) ||
+ ((c->argc-2) < -cmd->arity))
+ {
+ addReplyError(c,"Invalid number of arguments specified for command");
+ return;
+ }
+
+ if (!getKeysFromCommand(cmd,c->argv+2,c->argc-2,&result)) {
+ addReplyError(c,"Invalid arguments specified for command");
+ } else {
+ addReplyArrayLen(c,result.numkeys);
+ for (j = 0; j < result.numkeys; j++) addReplyBulk(c,c->argv[result.keys[j]+2]);
+ }
+ getKeysFreeResult(&result);
+}
+
+/* COMMAND (no args) */
void commandCommand(client *c) {
dictIterator *di;
dictEntry *de;
- if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
- const char *help[] = {
+ addReplyArrayLen(c, dictSize(server.commands));
+ di = dictGetIterator(server.commands);
+ while ((de = dictNext(di)) != NULL) {
+ addReplyCommand(c, dictGetVal(de));
+ }
+ dictReleaseIterator(di);
+}
+
+/* COMMAND COUNT */
+void commandCountCommand(client *c) {
+ addReplyLongLong(c, dictSize(server.commands));
+}
+
+typedef enum {
+ COMMAND_LIST_FILTER_MODULE,
+ COMMAND_LIST_FILTER_ACLCAT,
+ COMMAND_LIST_FILTER_PATTERN,
+} commandListFilterType;
+
+typedef struct {
+ commandListFilterType type;
+ sds arg;
+ struct {
+ int valid;
+ union {
+ uint64_t aclcat;
+ void *module_handle;
+ } u;
+ } cache;
+} commandListFilter;
+
+int shouldFilterFromCommandList(struct redisCommand *cmd, commandListFilter *filter) {
+ switch (filter->type) {
+ case (COMMAND_LIST_FILTER_MODULE):
+ if (!filter->cache.valid) {
+ filter->cache.u.module_handle = moduleGetHandleByName(filter->arg);
+ filter->cache.valid = 1;
+ }
+ return !moduleIsModuleCommand(filter->cache.u.module_handle, cmd);
+ case (COMMAND_LIST_FILTER_ACLCAT): {
+ if (!filter->cache.valid) {
+ filter->cache.u.aclcat = ACLGetCommandCategoryFlagByName(filter->arg);
+ filter->cache.valid = 1;
+ }
+ uint64_t cat = filter->cache.u.aclcat;
+ if (cat == 0)
+ return 1; /* Invalid ACL category */
+ return (!(cmd->flags & cat));
+ break;
+ }
+ case (COMMAND_LIST_FILTER_PATTERN):
+ return !stringmatchlen(filter->arg, sdslen(filter->arg), cmd->name, strlen(cmd->name), 1);
+ default:
+ serverPanic("Invalid filter type %d", filter->type);
+ }
+}
+
+/* COMMAND LIST [FILTERBY (MODULE <module-name>|ACLCAT <cat>|PATTERN <pattern>)] */
+void commandListCommand(client *c) {
+
+ /* Parse options. */
+ int i = 2, got_filter = 0;
+ commandListFilter filter = {0};
+ for (; i < c->argc; i++) {
+ int moreargs = (c->argc-1) - i; /* Number of additional arguments. */
+ char *opt = c->argv[i]->ptr;
+ if (!strcasecmp(opt,"filterby") && moreargs == 2) {
+ char *filtertype = c->argv[i+1]->ptr;
+ if (!strcasecmp(filtertype,"module")) {
+ filter.type = COMMAND_LIST_FILTER_MODULE;
+ } else if (!strcasecmp(filtertype,"aclcat")) {
+ filter.type = COMMAND_LIST_FILTER_ACLCAT;
+ } else if (!strcasecmp(filtertype,"pattern")) {
+ filter.type = COMMAND_LIST_FILTER_PATTERN;
+ } else {
+ addReplyErrorObject(c,shared.syntaxerr);
+ return;
+ }
+ got_filter = 1;
+ filter.arg = c->argv[i+2]->ptr;
+ i += 2;
+ } else {
+ addReplyErrorObject(c,shared.syntaxerr);
+ return;
+ }
+ }
+
+ dictIterator *di;
+ dictEntry *de;
+
+ di = dictGetIterator(server.commands);
+ if (!got_filter) {
+ addReplySetLen(c, dictSize(server.commands));
+ while ((de = dictNext(di)) != NULL) {
+ struct redisCommand *cmd = dictGetVal(de);
+ addReplyBulkCString(c,cmd->name);
+ }
+ } else {
+ int numcmds = 0;
+ 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);
+}
+
+/* COMMAND INFO <command-name> [<command-name> ...] */
+void commandInfoCommand(client *c) {
+ int i;
+ addReplyArrayLen(c, c->argc-2);
+ for (i = 2; i < c->argc; i++) {
+ addReplyCommand(c, lookupCommandBySds(c->argv[i]->ptr));
+ }
+}
+
+/* COMMAND GETKEYS arg0 arg1 arg2 ... */
+void commandGetKeysCommand(client *c) {
+ getKeysSubcommand(c);
+}
+
+/* COMMAND HELP */
+void commandHelpCommand(client *c) {
+ const char *help[] = {
"(no subcommand)",
" Return details about all Redis commands.",
"COUNT",
" Return the total number of commands in this Redis server.",
+"LIST",
+" Return a list of all commands in this Redis server.",
+"INFO <command-name> [<command-name> ...]",
+" Return details about multiple Redis commands.",
"GETKEYS <full-command>",
" Return the keys from a full Redis command.",
-"INFO [<command-name> ...]",
-" Return details about multiple Redis commands.",
NULL
- };
- addReplyHelp(c, help);
- } else if (c->argc == 1) {
- addReplyArrayLen(c, dictSize(server.commands));
- di = dictGetIterator(server.commands);
- while ((de = dictNext(di)) != NULL) {
- addReplyCommand(c, dictGetVal(de));
- }
- dictReleaseIterator(di);
- } else if (!strcasecmp(c->argv[1]->ptr, "info")) {
- int i;
- addReplyArrayLen(c, c->argc-2);
- for (i = 2; i < c->argc; i++) {
- addReplyCommand(c, dictFetchValue(server.commands, c->argv[i]->ptr));
- }
- } else if (!strcasecmp(c->argv[1]->ptr, "count") && c->argc == 2) {
- addReplyLongLong(c, dictSize(server.commands));
- } else if (!strcasecmp(c->argv[1]->ptr,"getkeys") && c->argc >= 3) {
- struct redisCommand *cmd = lookupCommand(c->argv[2]->ptr);
- getKeysResult result = GETKEYS_RESULT_INIT;
- int j;
-
- if (!cmd) {
- addReplyError(c,"Invalid command specified");
- return;
- } else if (cmd->getkeys_proc == NULL && cmd->key_specs_num == 0) {
- addReplyError(c,"The command has no key arguments");
- return;
- } else if ((cmd->arity > 0 && cmd->arity != c->argc-2) ||
- ((c->argc-2) < -cmd->arity))
- {
- addReplyError(c,"Invalid number of arguments specified for command");
- return;
- }
+ };
- if (!getKeysFromCommand(cmd,c->argv+2,c->argc-2,&result)) {
- addReplyError(c,"Invalid arguments specified for command");
- } else {
- addReplyArrayLen(c,result.numkeys);
- for (j = 0; j < result.numkeys; j++) addReplyBulk(c,c->argv[result.keys[j]+2]);
- }
- getKeysFreeResult(&result);
- } else {
- addReplySubcommandSyntaxError(c);
- }
+ addReplyHelp(c, help);
}
/* Convert an amount of bytes into a human readable string in the form
@@ -5356,6 +6077,14 @@ void bytesToHuman(char *s, unsigned long long n) {
}
}
+sds getFullCommandName(struct redisCommand *cmd) {
+ if (!cmd->parent) {
+ return sdsnew(cmd->name);
+ } else {
+ return sdscatfmt(sdsempty(),"%s|%s",cmd->parent->name,cmd->name);
+ }
+}
+
/* Characters we sanitize on INFO output to maintain expected format. */
static char unsafe_info_chars[] = "#:\n\r";
static char unsafe_info_chars_substs[] = "____"; /* Must be same length as above */
@@ -5375,6 +6104,35 @@ const char *getSafeInfoString(const char *s, size_t len, char **tmp) {
sizeof(unsafe_info_chars)-1);
}
+sds genRedisInfoStringCommandStats(sds info, dict *commands) {
+ struct redisCommand *c;
+ dictEntry *de;
+ dictIterator *di;
+ di = dictGetSafeIterator(commands);
+ while((de = dictNext(di)) != NULL) {
+ char *tmpsafe;
+ c = (struct redisCommand *) dictGetVal(de);
+ if (c->calls || c->failed_calls || c->rejected_calls) {
+ sds cmdnamesds = getFullCommandName(c);
+
+ info = sdscatprintf(info,
+ "cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f"
+ ",rejected_calls=%lld,failed_calls=%lld\r\n",
+ getSafeInfoString(cmdnamesds, sdslen(cmdnamesds), &tmpsafe), c->calls, c->microseconds,
+ (c->calls == 0) ? 0 : ((float)c->microseconds/c->calls),
+ c->rejected_calls, c->failed_calls);
+ if (tmpsafe != NULL) zfree(tmpsafe);
+ sdsfree(cmdnamesds);
+ }
+ if (c->subcommands_dict) {
+ info = genRedisInfoStringCommandStats(info, c->subcommands_dict);
+ }
+ }
+ dictReleaseIterator(di);
+
+ return info;
+}
+
/* Create the string returned by the INFO command. This is decoupled
* by the INFO command itself as we need to report the same information
* on memory corruption problems. */
@@ -6041,25 +6799,7 @@ sds genRedisInfoString(const char *section) {
if (allsections || !strcasecmp(section,"commandstats")) {
if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info, "# Commandstats\r\n");
-
- struct redisCommand *c;
- dictEntry *de;
- dictIterator *di;
- di = dictGetSafeIterator(server.commands);
- while((de = dictNext(di)) != NULL) {
- char *tmpsafe;
- c = (struct redisCommand *) dictGetVal(de);
- if (!c->calls && !c->failed_calls && !c->rejected_calls)
- continue;
- info = sdscatprintf(info,
- "cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f"
- ",rejected_calls=%lld,failed_calls=%lld\r\n",
- getSafeInfoString(c->name, strlen(c->name), &tmpsafe), c->calls, c->microseconds,
- (c->calls == 0) ? 0 : ((float)c->microseconds/c->calls),
- c->rejected_calls, c->failed_calls);
- if (tmpsafe != NULL) zfree(tmpsafe);
- }
- dictReleaseIterator(di);
+ info = genRedisInfoStringCommandStats(info, server.commands);
}
/* Error statistics */
if (allsections || defsections || !strcasecmp(section,"errorstats")) {
@@ -6120,6 +6860,11 @@ sds genRedisInfoString(const char *section) {
}
void infoCommand(client *c) {
+ if (server.sentinel_mode) {
+ sentinelInfoCommand(c);
+ return;
+ }
+
char *section = c->argc == 2 ? c->argv[1]->ptr : "default";
if (c->argc > 2) {