1 | /* |
2 | * Copyright (c) 2009-2021, Redis Ltd. |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are met: |
7 | * |
8 | * * Redistributions of source code must retain the above copyright notice, |
9 | * this list of conditions and the following disclaimer. |
10 | * * Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * * Neither the name of Redis nor the names of its contributors may be used |
14 | * to endorse or promote products derived from this software without |
15 | * specific prior written permission. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
27 | * POSSIBILITY OF SUCH DAMAGE. |
28 | */ |
29 | |
30 | #include "server.h" |
31 | #include "script.h" |
32 | #include "cluster.h" |
33 | |
34 | scriptFlag scripts_flags_def[] = { |
35 | {.flag = SCRIPT_FLAG_NO_WRITES, .str = "no-writes" }, |
36 | {.flag = SCRIPT_FLAG_ALLOW_OOM, .str = "allow-oom" }, |
37 | {.flag = SCRIPT_FLAG_ALLOW_STALE, .str = "allow-stale" }, |
38 | {.flag = SCRIPT_FLAG_NO_CLUSTER, .str = "no-cluster" }, |
39 | {.flag = SCRIPT_FLAG_ALLOW_CROSS_SLOT, .str = "allow-cross-slot-keys" }, |
40 | {.flag = 0, .str = NULL}, /* flags array end */ |
41 | }; |
42 | |
43 | /* On script invocation, holding the current run context */ |
44 | static scriptRunCtx *curr_run_ctx = NULL; |
45 | |
46 | static void exitScriptTimedoutMode(scriptRunCtx *run_ctx) { |
47 | serverAssert(run_ctx == curr_run_ctx); |
48 | serverAssert(scriptIsTimedout()); |
49 | run_ctx->flags &= ~SCRIPT_TIMEDOUT; |
50 | blockingOperationEnds(); |
51 | /* if we are a replica and we have an active master, set it for continue processing */ |
52 | if (server.masterhost && server.master) queueClientForReprocessing(server.master); |
53 | } |
54 | |
55 | static void enterScriptTimedoutMode(scriptRunCtx *run_ctx) { |
56 | serverAssert(run_ctx == curr_run_ctx); |
57 | serverAssert(!scriptIsTimedout()); |
58 | /* Mark script as timedout */ |
59 | run_ctx->flags |= SCRIPT_TIMEDOUT; |
60 | blockingOperationStarts(); |
61 | } |
62 | |
63 | int scriptIsTimedout() { |
64 | return scriptIsRunning() && (curr_run_ctx->flags & SCRIPT_TIMEDOUT); |
65 | } |
66 | |
67 | client* scriptGetClient() { |
68 | serverAssert(scriptIsRunning()); |
69 | return curr_run_ctx->c; |
70 | } |
71 | |
72 | client* scriptGetCaller() { |
73 | serverAssert(scriptIsRunning()); |
74 | return curr_run_ctx->original_client; |
75 | } |
76 | |
77 | /* interrupt function for scripts, should be call |
78 | * from time to time to reply some special command (like ping) |
79 | * and also check if the run should be terminated. */ |
80 | int scriptInterrupt(scriptRunCtx *run_ctx) { |
81 | if (run_ctx->flags & SCRIPT_TIMEDOUT) { |
82 | /* script already timedout |
83 | we just need to precess some events and return */ |
84 | processEventsWhileBlocked(); |
85 | return (run_ctx->flags & SCRIPT_KILLED) ? SCRIPT_KILL : SCRIPT_CONTINUE; |
86 | } |
87 | |
88 | long long elapsed = elapsedMs(run_ctx->start_time); |
89 | if (elapsed < server.busy_reply_threshold) { |
90 | return SCRIPT_CONTINUE; |
91 | } |
92 | |
93 | serverLog(LL_WARNING, |
94 | "Slow script detected: still in execution after %lld milliseconds. " |
95 | "You can try killing the script using the %s command. Script name is: %s." , |
96 | elapsed, (run_ctx->flags & SCRIPT_EVAL_MODE) ? "SCRIPT KILL" : "FUNCTION KILL" , run_ctx->funcname); |
97 | |
98 | enterScriptTimedoutMode(run_ctx); |
99 | /* Once the script timeouts we reenter the event loop to permit others |
100 | * some commands execution. For this reason |
101 | * we need to mask the client executing the script from the event loop. |
102 | * If we don't do that the client may disconnect and could no longer be |
103 | * here when the EVAL command will return. */ |
104 | protectClient(run_ctx->original_client); |
105 | |
106 | processEventsWhileBlocked(); |
107 | |
108 | return (run_ctx->flags & SCRIPT_KILLED) ? SCRIPT_KILL : SCRIPT_CONTINUE; |
109 | } |
110 | |
111 | uint64_t scriptFlagsToCmdFlags(uint64_t cmd_flags, uint64_t script_flags) { |
112 | /* If the script declared flags, clear the ones from the command and use the ones it declared.*/ |
113 | cmd_flags &= ~(CMD_STALE | CMD_DENYOOM | CMD_WRITE); |
114 | |
115 | /* NO_WRITES implies ALLOW_OOM */ |
116 | if (!(script_flags & (SCRIPT_FLAG_ALLOW_OOM | SCRIPT_FLAG_NO_WRITES))) |
117 | cmd_flags |= CMD_DENYOOM; |
118 | if (!(script_flags & SCRIPT_FLAG_NO_WRITES)) |
119 | cmd_flags |= CMD_WRITE; |
120 | if (script_flags & SCRIPT_FLAG_ALLOW_STALE) |
121 | cmd_flags |= CMD_STALE; |
122 | |
123 | /* In addition the MAY_REPLICATE flag is set for these commands, but |
124 | * if we have flags we know if it's gonna do any writes or not. */ |
125 | cmd_flags &= ~CMD_MAY_REPLICATE; |
126 | |
127 | return cmd_flags; |
128 | } |
129 | |
130 | /* Prepare the given run ctx for execution */ |
131 | int scriptPrepareForRun(scriptRunCtx *run_ctx, client *engine_client, client *caller, const char *funcname, uint64_t script_flags, int ro) { |
132 | serverAssert(!curr_run_ctx); |
133 | |
134 | int running_stale = server.masterhost && |
135 | server.repl_state != REPL_STATE_CONNECTED && |
136 | server.repl_serve_stale_data == 0; |
137 | int obey_client = mustObeyClient(caller); |
138 | |
139 | if (!(script_flags & SCRIPT_FLAG_EVAL_COMPAT_MODE)) { |
140 | if ((script_flags & SCRIPT_FLAG_NO_CLUSTER) && server.cluster_enabled) { |
141 | addReplyError(caller, "Can not run script on cluster, 'no-cluster' flag is set." ); |
142 | return C_ERR; |
143 | } |
144 | |
145 | if (running_stale && !(script_flags & SCRIPT_FLAG_ALLOW_STALE)) { |
146 | addReplyError(caller, "-MASTERDOWN Link with MASTER is down, " |
147 | "replica-serve-stale-data is set to 'no' " |
148 | "and 'allow-stale' flag is not set on the script." ); |
149 | return C_ERR; |
150 | } |
151 | |
152 | if (!(script_flags & SCRIPT_FLAG_NO_WRITES)) { |
153 | /* Script may perform writes we need to verify: |
154 | * 1. we are not a readonly replica |
155 | * 2. no disk error detected |
156 | * 3. command is not `fcall_ro`/`eval[sha]_ro` */ |
157 | if (server.masterhost && server.repl_slave_ro && !obey_client) { |
158 | addReplyError(caller, "-READONLY Can not run script with write flag on readonly replica" ); |
159 | return C_ERR; |
160 | } |
161 | |
162 | /* Deny writes if we're unale to persist. */ |
163 | int deny_write_type = writeCommandsDeniedByDiskError(); |
164 | if (deny_write_type != DISK_ERROR_TYPE_NONE && !obey_client) { |
165 | if (deny_write_type == DISK_ERROR_TYPE_RDB) |
166 | addReplyError(caller, "-MISCONF Redis is configured to save RDB snapshots, " |
167 | "but it's currently unable to persist to disk. " |
168 | "Writable scripts are blocked. Use 'no-writes' flag for read only scripts." ); |
169 | else |
170 | addReplyErrorFormat(caller, "-MISCONF Redis is configured to persist data to AOF, " |
171 | "but it's currently unable to persist to disk. " |
172 | "Writable scripts are blocked. Use 'no-writes' flag for read only scripts. " |
173 | "AOF error: %s" , strerror(server.aof_last_write_errno)); |
174 | return C_ERR; |
175 | } |
176 | |
177 | if (ro) { |
178 | addReplyError(caller, "Can not execute a script with write flag using *_ro command." ); |
179 | return C_ERR; |
180 | } |
181 | |
182 | /* Don't accept write commands if there are not enough good slaves and |
183 | * user configured the min-slaves-to-write option. */ |
184 | if (server.masterhost == NULL && |
185 | server.repl_min_slaves_max_lag && |
186 | server.repl_min_slaves_to_write && |
187 | server.repl_good_slaves_count < server.repl_min_slaves_to_write) |
188 | { |
189 | addReplyErrorObject(caller, shared.noreplicaserr); |
190 | return C_ERR; |
191 | } |
192 | } |
193 | |
194 | /* Check OOM state. the no-writes flag imply allow-oom. we tested it |
195 | * after the no-write error, so no need to mention it in the error reply. */ |
196 | if (server.pre_command_oom_state && server.maxmemory && |
197 | !(script_flags & (SCRIPT_FLAG_ALLOW_OOM|SCRIPT_FLAG_NO_WRITES))) |
198 | { |
199 | addReplyError(caller, "-OOM allow-oom flag is not set on the script, " |
200 | "can not run it when used memory > 'maxmemory'" ); |
201 | return C_ERR; |
202 | } |
203 | |
204 | } else { |
205 | /* Special handling for backwards compatibility (no shebang eval[sha]) mode */ |
206 | if (running_stale) { |
207 | addReplyErrorObject(caller, shared.masterdownerr); |
208 | return C_ERR; |
209 | } |
210 | } |
211 | |
212 | run_ctx->c = engine_client; |
213 | run_ctx->original_client = caller; |
214 | run_ctx->funcname = funcname; |
215 | |
216 | client *script_client = run_ctx->c; |
217 | client *curr_client = run_ctx->original_client; |
218 | server.script_caller = curr_client; |
219 | |
220 | /* Select the right DB in the context of the Lua client */ |
221 | selectDb(script_client, curr_client->db->id); |
222 | script_client->resp = 2; /* Default is RESP2, scripts can change it. */ |
223 | |
224 | /* If we are in MULTI context, flag Lua client as CLIENT_MULTI. */ |
225 | if (curr_client->flags & CLIENT_MULTI) { |
226 | script_client->flags |= CLIENT_MULTI; |
227 | } |
228 | |
229 | run_ctx->start_time = getMonotonicUs(); |
230 | run_ctx->snapshot_time = mstime(); |
231 | |
232 | run_ctx->flags = 0; |
233 | run_ctx->repl_flags = PROPAGATE_AOF | PROPAGATE_REPL; |
234 | |
235 | if (ro || (!(script_flags & SCRIPT_FLAG_EVAL_COMPAT_MODE) && (script_flags & SCRIPT_FLAG_NO_WRITES))) { |
236 | /* On fcall_ro or on functions that do not have the 'write' |
237 | * flag, we will not allow write commands. */ |
238 | run_ctx->flags |= SCRIPT_READ_ONLY; |
239 | } |
240 | if (!(script_flags & SCRIPT_FLAG_EVAL_COMPAT_MODE) && (script_flags & SCRIPT_FLAG_ALLOW_OOM)) { |
241 | /* Note: we don't need to test the no-writes flag here and set this run_ctx flag, |
242 | * since only write commands can are deny-oom. */ |
243 | run_ctx->flags |= SCRIPT_ALLOW_OOM; |
244 | } |
245 | |
246 | if ((script_flags & SCRIPT_FLAG_EVAL_COMPAT_MODE) || (script_flags & SCRIPT_FLAG_ALLOW_CROSS_SLOT)) { |
247 | run_ctx->flags |= SCRIPT_ALLOW_CROSS_SLOT; |
248 | } |
249 | |
250 | /* set the curr_run_ctx so we can use it to kill the script if needed */ |
251 | curr_run_ctx = run_ctx; |
252 | |
253 | return C_OK; |
254 | } |
255 | |
256 | /* Reset the given run ctx after execution */ |
257 | void scriptResetRun(scriptRunCtx *run_ctx) { |
258 | serverAssert(curr_run_ctx); |
259 | |
260 | /* After the script done, remove the MULTI state. */ |
261 | run_ctx->c->flags &= ~CLIENT_MULTI; |
262 | |
263 | server.script_caller = NULL; |
264 | |
265 | if (scriptIsTimedout()) { |
266 | exitScriptTimedoutMode(run_ctx); |
267 | /* Restore the client that was protected when the script timeout |
268 | * was detected. */ |
269 | unprotectClient(run_ctx->original_client); |
270 | } |
271 | |
272 | preventCommandPropagation(run_ctx->original_client); |
273 | |
274 | /* unset curr_run_ctx so we will know there is no running script */ |
275 | curr_run_ctx = NULL; |
276 | } |
277 | |
278 | /* return true if a script is currently running */ |
279 | int scriptIsRunning() { |
280 | return curr_run_ctx != NULL; |
281 | } |
282 | |
283 | const char* scriptCurrFunction() { |
284 | serverAssert(scriptIsRunning()); |
285 | return curr_run_ctx->funcname; |
286 | } |
287 | |
288 | int scriptIsEval() { |
289 | serverAssert(scriptIsRunning()); |
290 | return curr_run_ctx->flags & SCRIPT_EVAL_MODE; |
291 | } |
292 | |
293 | /* Kill the current running script */ |
294 | void scriptKill(client *c, int is_eval) { |
295 | if (!curr_run_ctx) { |
296 | addReplyError(c, "-NOTBUSY No scripts in execution right now." ); |
297 | return; |
298 | } |
299 | if (mustObeyClient(curr_run_ctx->original_client)) { |
300 | addReplyError(c, |
301 | "-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed." ); |
302 | } |
303 | if (curr_run_ctx->flags & SCRIPT_WRITE_DIRTY) { |
304 | addReplyError(c, |
305 | "-UNKILLABLE Sorry the script already executed write " |
306 | "commands against the dataset. You can either wait the " |
307 | "script termination or kill the server in a hard way " |
308 | "using the SHUTDOWN NOSAVE command." ); |
309 | return; |
310 | } |
311 | if (is_eval && !(curr_run_ctx->flags & SCRIPT_EVAL_MODE)) { |
312 | /* Kill a function with 'SCRIPT KILL' is not allow */ |
313 | addReplyErrorObject(c, shared.slowscripterr); |
314 | return; |
315 | } |
316 | if (!is_eval && (curr_run_ctx->flags & SCRIPT_EVAL_MODE)) { |
317 | /* Kill an eval with 'FUNCTION KILL' is not allow */ |
318 | addReplyErrorObject(c, shared.slowevalerr); |
319 | return; |
320 | } |
321 | curr_run_ctx->flags |= SCRIPT_KILLED; |
322 | addReply(c, shared.ok); |
323 | } |
324 | |
325 | static int scriptVerifyCommandArity(struct redisCommand *cmd, int argc, sds *err) { |
326 | if (!cmd || ((cmd->arity > 0 && cmd->arity != argc) || (argc < -cmd->arity))) { |
327 | if (cmd) |
328 | *err = sdsnew("Wrong number of args calling Redis command from script" ); |
329 | else |
330 | *err = sdsnew("Unknown Redis command called from script" ); |
331 | return C_ERR; |
332 | } |
333 | return C_OK; |
334 | } |
335 | |
336 | static int scriptVerifyACL(client *c, sds *err) { |
337 | /* Check the ACLs. */ |
338 | int acl_errpos; |
339 | int acl_retval = ACLCheckAllPerm(c, &acl_errpos); |
340 | if (acl_retval != ACL_OK) { |
341 | addACLLogEntry(c,acl_retval,ACL_LOG_CTX_LUA,acl_errpos,NULL,NULL); |
342 | *err = sdscatfmt(sdsempty(), "The user executing the script %s" , getAclErrorMessage(acl_retval)); |
343 | return C_ERR; |
344 | } |
345 | return C_OK; |
346 | } |
347 | |
348 | static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) { |
349 | |
350 | /* A write command, on an RO command or an RO script is rejected ASAP. |
351 | * Note: For scripts, we consider may-replicate commands as write commands. |
352 | * This also makes it possible to allow read-only scripts to be run during |
353 | * CLIENT PAUSE WRITE. */ |
354 | if (run_ctx->flags & SCRIPT_READ_ONLY && |
355 | (run_ctx->c->cmd->flags & (CMD_WRITE|CMD_MAY_REPLICATE))) |
356 | { |
357 | *err = sdsnew("Write commands are not allowed from read-only scripts." ); |
358 | return C_ERR; |
359 | } |
360 | |
361 | /* The other checks below are on the server state and are only relevant for |
362 | * write commands, return if this is not a write command. */ |
363 | if (!(run_ctx->c->cmd->flags & CMD_WRITE)) |
364 | return C_OK; |
365 | |
366 | /* If the script already made a modification to the dataset, we can't |
367 | * fail it on unpredictable error state. */ |
368 | if ((run_ctx->flags & SCRIPT_WRITE_DIRTY)) |
369 | return C_OK; |
370 | |
371 | /* Write commands are forbidden against read-only slaves, or if a |
372 | * command marked as non-deterministic was already called in the context |
373 | * of this script. */ |
374 | int deny_write_type = writeCommandsDeniedByDiskError(); |
375 | |
376 | if (server.masterhost && server.repl_slave_ro && |
377 | !mustObeyClient(run_ctx->original_client)) |
378 | { |
379 | *err = sdsdup(shared.roslaveerr->ptr); |
380 | return C_ERR; |
381 | } |
382 | |
383 | if (deny_write_type != DISK_ERROR_TYPE_NONE) { |
384 | *err = writeCommandsGetDiskErrorMessage(deny_write_type); |
385 | return C_ERR; |
386 | } |
387 | |
388 | /* Don't accept write commands if there are not enough good slaves and |
389 | * user configured the min-slaves-to-write option. Note this only reachable |
390 | * for Eval scripts that didn't declare flags, see the other check in |
391 | * scriptPrepareForRun */ |
392 | if (!checkGoodReplicasStatus()) { |
393 | *err = sdsdup(shared.noreplicaserr->ptr); |
394 | return C_ERR; |
395 | } |
396 | |
397 | return C_OK; |
398 | } |
399 | |
400 | static int scriptVerifyOOM(scriptRunCtx *run_ctx, char **err) { |
401 | if (run_ctx->flags & SCRIPT_ALLOW_OOM) { |
402 | /* Allow running any command even if OOM reached */ |
403 | return C_OK; |
404 | } |
405 | |
406 | /* If we reached the memory limit configured via maxmemory, commands that |
407 | * could enlarge the memory usage are not allowed, but only if this is the |
408 | * first write in the context of this script, otherwise we can't stop |
409 | * in the middle. */ |
410 | |
411 | if (server.maxmemory && /* Maxmemory is actually enabled. */ |
412 | !mustObeyClient(run_ctx->original_client) && /* Don't care about mem for replicas or AOF. */ |
413 | !(run_ctx->flags & SCRIPT_WRITE_DIRTY) && /* Script had no side effects so far. */ |
414 | server.pre_command_oom_state && /* Detected OOM when script start. */ |
415 | (run_ctx->c->cmd->flags & CMD_DENYOOM)) |
416 | { |
417 | *err = sdsdup(shared.oomerr->ptr); |
418 | return C_ERR; |
419 | } |
420 | |
421 | return C_OK; |
422 | } |
423 | |
424 | static int scriptVerifyClusterState(scriptRunCtx *run_ctx, client *c, client *original_c, sds *err) { |
425 | if (!server.cluster_enabled || mustObeyClient(original_c)) { |
426 | return C_OK; |
427 | } |
428 | /* If this is a Redis Cluster node, we need to make sure the script is not |
429 | * trying to access non-local keys, with the exception of commands |
430 | * received from our master or when loading the AOF back in memory. */ |
431 | int error_code; |
432 | /* Duplicate relevant flags in the script client. */ |
433 | c->flags &= ~(CLIENT_READONLY | CLIENT_ASKING); |
434 | c->flags |= original_c->flags & (CLIENT_READONLY | CLIENT_ASKING); |
435 | int hashslot = -1; |
436 | if (getNodeByQuery(c, c->cmd, c->argv, c->argc, &hashslot, &error_code) != server.cluster->myself) { |
437 | if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) { |
438 | *err = sdsnew( |
439 | "Script attempted to execute a write command while the " |
440 | "cluster is down and readonly" ); |
441 | } else if (error_code == CLUSTER_REDIR_DOWN_STATE) { |
442 | *err = sdsnew("Script attempted to execute a command while the " |
443 | "cluster is down" ); |
444 | } else { |
445 | *err = sdsnew("Script attempted to access a non local key in a " |
446 | "cluster node" ); |
447 | } |
448 | return C_ERR; |
449 | } |
450 | |
451 | /* If the script declared keys in advanced, the cross slot error would have |
452 | * already been thrown. This is only checking for cross slot keys being accessed |
453 | * that weren't pre-declared. */ |
454 | if (hashslot != -1 && !(run_ctx->flags & SCRIPT_ALLOW_CROSS_SLOT)) { |
455 | if (original_c->slot == -1) { |
456 | original_c->slot = hashslot; |
457 | } else if (original_c->slot != hashslot) { |
458 | *err = sdsnew("Script attempted to access keys that do not hash to " |
459 | "the same slot" ); |
460 | return C_ERR; |
461 | } |
462 | } |
463 | return C_OK; |
464 | } |
465 | |
466 | /* set RESP for a given run_ctx */ |
467 | int scriptSetResp(scriptRunCtx *run_ctx, int resp) { |
468 | if (resp != 2 && resp != 3) { |
469 | return C_ERR; |
470 | } |
471 | |
472 | run_ctx->c->resp = resp; |
473 | return C_OK; |
474 | } |
475 | |
476 | /* set Repl for a given run_ctx |
477 | * either: PROPAGATE_AOF | PROPAGATE_REPL*/ |
478 | int scriptSetRepl(scriptRunCtx *run_ctx, int repl) { |
479 | if ((repl & ~(PROPAGATE_AOF | PROPAGATE_REPL)) != 0) { |
480 | return C_ERR; |
481 | } |
482 | run_ctx->repl_flags = repl; |
483 | return C_OK; |
484 | } |
485 | |
486 | static int scriptVerifyAllowStale(client *c, sds *err) { |
487 | if (!server.masterhost) { |
488 | /* Not a replica, stale is irrelevant */ |
489 | return C_OK; |
490 | } |
491 | |
492 | if (server.repl_state == REPL_STATE_CONNECTED) { |
493 | /* Connected to replica, stale is irrelevant */ |
494 | return C_OK; |
495 | } |
496 | |
497 | if (server.repl_serve_stale_data == 1) { |
498 | /* Disconnected from replica but allow to serve data */ |
499 | return C_OK; |
500 | } |
501 | |
502 | if (c->cmd->flags & CMD_STALE) { |
503 | /* Command is allow while stale */ |
504 | return C_OK; |
505 | } |
506 | |
507 | /* On stale replica, can not run the command */ |
508 | *err = sdsnew("Can not execute the command on a stale replica" ); |
509 | return C_ERR; |
510 | } |
511 | |
512 | /* Call a Redis command. |
513 | * The reply is written to the run_ctx client and it is |
514 | * up to the engine to take and parse. |
515 | * The err out variable is set only if error occurs and describe the error. |
516 | * If err is set on reply is written to the run_ctx client. */ |
517 | void scriptCall(scriptRunCtx *run_ctx, robj* *argv, int argc, sds *err) { |
518 | client *c = run_ctx->c; |
519 | |
520 | /* Setup our fake client for command execution */ |
521 | c->argv = argv; |
522 | c->argc = argc; |
523 | c->user = run_ctx->original_client->user; |
524 | |
525 | /* Process module hooks */ |
526 | moduleCallCommandFilters(c); |
527 | argv = c->argv; |
528 | argc = c->argc; |
529 | |
530 | struct redisCommand *cmd = lookupCommand(argv, argc); |
531 | c->cmd = c->lastcmd = c->realcmd = cmd; |
532 | if (scriptVerifyCommandArity(cmd, argc, err) != C_OK) { |
533 | goto error; |
534 | } |
535 | |
536 | /* There are commands that are not allowed inside scripts. */ |
537 | if (!server.script_disable_deny_script && (cmd->flags & CMD_NOSCRIPT)) { |
538 | *err = sdsnew("This Redis command is not allowed from script" ); |
539 | goto error; |
540 | } |
541 | |
542 | if (scriptVerifyAllowStale(c, err) != C_OK) { |
543 | goto error; |
544 | } |
545 | |
546 | if (scriptVerifyACL(c, err) != C_OK) { |
547 | goto error; |
548 | } |
549 | |
550 | if (scriptVerifyWriteCommandAllow(run_ctx, err) != C_OK) { |
551 | goto error; |
552 | } |
553 | |
554 | if (scriptVerifyOOM(run_ctx, err) != C_OK) { |
555 | goto error; |
556 | } |
557 | |
558 | if (cmd->flags & CMD_WRITE) { |
559 | /* signify that we already change the data in this execution */ |
560 | run_ctx->flags |= SCRIPT_WRITE_DIRTY; |
561 | } |
562 | |
563 | if (scriptVerifyClusterState(run_ctx, c, run_ctx->original_client, err) != C_OK) { |
564 | goto error; |
565 | } |
566 | |
567 | int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS; |
568 | if (run_ctx->repl_flags & PROPAGATE_AOF) { |
569 | call_flags |= CMD_CALL_PROPAGATE_AOF; |
570 | } |
571 | if (run_ctx->repl_flags & PROPAGATE_REPL) { |
572 | call_flags |= CMD_CALL_PROPAGATE_REPL; |
573 | } |
574 | call(c, call_flags); |
575 | serverAssert((c->flags & CLIENT_BLOCKED) == 0); |
576 | return; |
577 | |
578 | error: |
579 | afterErrorReply(c, *err, sdslen(*err), 0); |
580 | incrCommandStatsOnError(cmd, ERROR_COMMAND_REJECTED); |
581 | } |
582 | |
583 | /* Returns the time when the script invocation started */ |
584 | mstime_t scriptTimeSnapshot() { |
585 | serverAssert(curr_run_ctx); |
586 | return curr_run_ctx->snapshot_time; |
587 | } |
588 | |
589 | long long scriptRunDuration() { |
590 | serverAssert(scriptIsRunning()); |
591 | return elapsedMs(curr_run_ctx->start_time); |
592 | } |
593 | |