Commit a1ec0cae authored by Shaya Potter's avatar Shaya Potter Committed by Oran Agra
Browse files

Improve cmd_flags for script/functions in RM_Call (#11159)

When RM_Call was used with `M` (reject OOM), `W` (reject writes),
as well as `S` (rejecting stale or write commands in "Script mode"),
it would have only checked the command flags, but not the declared
script flag in case it's a command that runs a script.

Refactoring: extracts out similar code in server.c's processCommand
to be usable in RM_Call as well.

(cherry picked from commit bed6d759)
parent b8beda61
......@@ -592,6 +592,7 @@ void moduleReleaseTempClient(client *c) {
c->bufpos = 0;
c->flags = CLIENT_MODULE;
c->user = NULL; /* Root user */
c->cmd = c->lastcmd = c->realcmd = NULL;
moduleTempClients[moduleTempClientCount++] = c;
}
......@@ -5759,7 +5760,6 @@ fmterr:
* This API is documented here: https://redis.io/topics/modules-intro
*/
RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
struct redisCommand *cmd;
client *c = NULL;
robj **argv = NULL;
int argc = 0, argv_len = 0, flags = 0;
......@@ -5767,6 +5767,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
RedisModuleCallReply *reply = NULL;
int replicate = 0; /* Replicate this command? */
int error_as_call_replies = 0; /* return errors as RedisModuleCallReply object */
uint64_t cmd_flags;
/* Handle arguments. */
va_start(ap, fmt);
......@@ -5809,7 +5810,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
/* Lookup command now, after filters had a chance to make modifications
* if necessary.
*/
cmd = c->cmd = c->lastcmd = c->realcmd = lookupCommand(c->argv,c->argc);
c->cmd = c->lastcmd = c->realcmd = lookupCommand(c->argv,c->argc);
sds err;
if (!commandCheckExistence(c, error_as_call_replies? &err : NULL)) {
errno = ENOENT;
......@@ -5824,10 +5825,12 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
goto cleanup;
}
cmd_flags = getCommandFlags(c);
if (flags & REDISMODULE_ARGV_SCRIPT_MODE) {
/* Basically on script mode we want to only allow commands that can
* be executed on scripts (CMD_NOSCRIPT is not set on the command flags) */
if (cmd->flags & CMD_NOSCRIPT) {
if (cmd_flags & CMD_NOSCRIPT) {
errno = ESPIPE;
if (error_as_call_replies) {
sds msg = sdscatfmt(sdsempty(), "command '%S' is not allowed on script mode", c->cmd->fullname);
......@@ -5838,7 +5841,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
}
if (flags & REDISMODULE_ARGV_RESPECT_DENY_OOM) {
if (cmd->flags & CMD_DENYOOM) {
if (cmd_flags & CMD_DENYOOM) {
int oom_state;
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) {
/* On background thread we can not count on server.pre_command_oom_state.
......@@ -5860,7 +5863,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
}
if (flags & REDISMODULE_ARGV_NO_WRITES) {
if (cmd->flags & CMD_WRITE) {
if (cmd_flags & CMD_WRITE) {
errno = ENOSPC;
if (error_as_call_replies) {
sds msg = sdscatfmt(sdsempty(), "Write command '%S' was "
......@@ -5873,7 +5876,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
/* Script mode tests */
if (flags & REDISMODULE_ARGV_SCRIPT_MODE) {
if (cmd->flags & CMD_WRITE) {
if (cmd_flags & CMD_WRITE) {
/* on script mode, if a command is a write command,
* We will not run it if we encounter disk error
* or we do not have enough replicas */
......@@ -5910,7 +5913,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
}
if (server.masterhost && server.repl_state != REPL_STATE_CONNECTED &&
server.repl_serve_stale_data == 0 && !(cmd->flags & CMD_STALE)) {
server.repl_serve_stale_data == 0 && !(cmd_flags & CMD_STALE)) {
errno = ESPIPE;
if (error_as_call_replies) {
sds msg = sdsdup(shared.masterdownerr->ptr);
......
......@@ -3584,6 +3584,23 @@ int commandCheckArity(client *c, sds *err) {
return 1;
}
/* If we're executing a script, try to extract a set of command flags from
* it, in case it declared them. Note this is just an attempt, we don't yet
* know the script command is well formed.*/
uint64_t getCommandFlags(client *c) {
uint64_t cmd_flags = c->cmd->flags;
if (c->cmd->proc == fcallCommand || c->cmd->proc == fcallroCommand) {
cmd_flags = fcallGetCommandFlags(c, cmd_flags);
} else if (c->cmd->proc == evalCommand || c->cmd->proc == evalRoCommand ||
c->cmd->proc == evalShaCommand || c->cmd->proc == evalShaRoCommand)
{
cmd_flags = evalGetCommandFlags(c, cmd_flags);
}
return cmd_flags;
}
/* If this function gets called we already read a whole
* command, arguments are in the client argv/argc fields.
* processCommand() execute the command or prepare the
......@@ -3648,19 +3665,7 @@ int processCommand(client *c) {
}
}
/* If we're executing a script, try to extract a set of command flags from
* it, in case it declared them. Note this is just an attempt, we don't yet
* know the script command is well formed.*/
uint64_t cmd_flags = c->cmd->flags;
if (c->cmd->proc == evalCommand || c->cmd->proc == evalShaCommand ||
c->cmd->proc == evalRoCommand || c->cmd->proc == evalShaRoCommand ||
c->cmd->proc == fcallCommand || c->cmd->proc == fcallroCommand)
{
if (c->cmd->proc == fcallCommand || c->cmd->proc == fcallroCommand)
cmd_flags = fcallGetCommandFlags(c, cmd_flags);
else
cmd_flags = evalGetCommandFlags(c, cmd_flags);
}
uint64_t cmd_flags = getCommandFlags(c);
int is_read_command = (cmd_flags & CMD_READONLY) ||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_READONLY));
......
......@@ -2876,6 +2876,7 @@ int zslLexValueLteMax(sds value, zlexrangespec *spec);
int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level);
size_t freeMemoryGetNotCountedMemory();
int overMaxmemoryAfterAlloc(size_t moremem);
uint64_t getCommandFlags(client *c);
int processCommand(client *c);
int processPendingCommandAndInputBuffer(client *c);
void setupSignalHandlers(void);
......
......@@ -173,6 +173,29 @@ start_server {tags {"modules"}} {
r config set maxmemory 0
} {OK} {needs:config-maxmemory}
test {rm_call OOM Eval} {
r config set maxmemory 1
r config set maxmemory-policy volatile-lru
# use the M flag without allow-oom shebang flag
assert_error {OOM *} {
r test.rm_call_flags M eval {#!lua
redis.call('set','x',1)
return 1
} 1 x
}
# add the M flag with allow-oom shebang flag
assert_equal {1} [
r test.rm_call_flags M eval {#!lua flags=allow-oom
redis.call('set','x',1)
return 1
} 1 x
]
r config set maxmemory 0
} {OK} {needs:config-maxmemory}
test {rm_call write flag} {
# add the W flag
assert_error {ERR Write command 'set' was called while write is not allowed.} {
......
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