diff --git a/README.md b/README.md index 308bbcb98..f84ba2504 100644 --- a/README.md +++ b/README.md @@ -443,6 +443,16 @@ This file also implements both the `SYNC` and `PSYNC` commands that are used in order to perform the first synchronization between masters and replicas, or to continue the replication after a disconnection. +Script +--- +The script unit is compose of 3 units +* `script.c` - integration of scripts with Redis (commands execution, set replication/resp, ..) +* `script_lua.c` - responsible to execute Lua code, uses script.c to interact with Redis from within the Lua code. +* `function_lua.c` - contains the Lua engine implementation, uses script_lua.c to execute the Lua code. +* `functions.c` - Contains Redis Functions implementation (FUNCTION command), uses functions_lua.c if the function it wants to invoke needs the Lua engine. +* `eval.c` - Contains the `eval` implementation using `script_lua.c` to invoke the Lua code. + + Other C files --- @@ -451,7 +461,6 @@ Other C files * `sds.c` is the Redis string library, check https://github.com/antirez/sds for more information. * `anet.c` is a library to use POSIX networking in a simpler way compared to the raw interface exposed by the kernel. * `dict.c` is an implementation of a non-blocking hash table which rehashes incrementally. -* `scripting.c` implements Lua scripting. It is completely self-contained and isolated from the rest of the Redis implementation and is simple enough to understand if you are familiar with the Lua API. * `cluster.c` implements the Redis Cluster. Probably a good read only after being very familiar with the rest of the Redis code base. If you want to read `cluster.c` make sure to read the [Redis Cluster specification][4]. [4]: https://redis.io/topics/cluster-spec diff --git a/src/Makefile b/src/Makefile index 34b5c3566..1bc6d86ed 100644 --- a/src/Makefile +++ b/src/Makefile @@ -309,7 +309,7 @@ endif REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX) REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX) -REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o connection.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o +REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o connection.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX) REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX) diff --git a/src/acl.c b/src/acl.c index 5d1598b39..b9efd5401 100644 --- a/src/acl.c +++ b/src/acl.c @@ -1897,7 +1897,7 @@ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username } client *realclient = c; - if (realclient->flags & CLIENT_LUA) realclient = server.lua_caller; + if (realclient->flags & CLIENT_SCRIPT) realclient = server.script_caller; le->cinfo = catClientInfoString(sdsempty(),realclient); le->context = context; diff --git a/src/aof.c b/src/aof.c index 4b9900ab2..8f609edc6 100644 --- a/src/aof.c +++ b/src/aof.c @@ -30,6 +30,7 @@ #include "server.h" #include "bio.h" #include "rio.h" +#include "functions.h" #include #include @@ -754,7 +755,8 @@ int loadAppendOnlyFile(char *filename) { serverLog(LL_NOTICE,"Reading RDB preamble from AOF file..."); if (fseek(fp,0,SEEK_SET) == -1) goto readerr; rioInitWithFile(&rdb,fp); - if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL,server.db) != C_OK) { + + if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL) != C_OK) { serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted"); goto readerr; } else { diff --git a/src/config.c b/src/config.c index 2b2ff737d..939a5342c 100644 --- a/src/config.c +++ b/src/config.c @@ -2650,7 +2650,7 @@ standardConfig configs[] = { createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL), /* Long Long configs */ - createLongLongConfig("lua-time-limit", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.lua_time_limit, 5000, INTEGER_CONFIG, NULL, NULL),/* milliseconds */ + createLongLongConfig("script-time-limit", "lua-time-limit", MODIFIABLE_CONFIG, 0, LONG_MAX, server.script_time_limit, 5000, INTEGER_CONFIG, NULL, NULL),/* milliseconds */ createLongLongConfig("cluster-node-timeout", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.cluster_node_timeout, 15000, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("slowlog-log-slower-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, server.slowlog_log_slower_than, 10000, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("latency-monitor-threshold", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL), diff --git a/src/db.c b/src/db.c index b98310cf3..d0c8b5903 100644 --- a/src/db.c +++ b/src/db.c @@ -31,6 +31,7 @@ #include "cluster.h" #include "atomicvar.h" #include "latency.h" +#include "script.h" #include #include @@ -88,7 +89,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) { * commands is to make writable replicas behave consistently. It * shall not be used in readonly commands. Modules are accepted so * that we don't break old modules. */ - client *c = server.in_eval ? server.lua_client : server.current_client; + client *c = server.in_script ? scriptGetClient() : server.current_client; serverAssert(!c || !c->cmd || (c->cmd->flags & (CMD_WRITE|CMD_MODULE))); } if (expireIfNeeded(db, key, force_delete_expired)) { @@ -1501,8 +1502,8 @@ int keyIsExpired(redisDb *db, robj *key) { * only the first time it is accessed and not in the middle of the * script execution, making propagation to slaves / AOF consistent. * See issue #1525 on Github for more information. */ - if (server.lua_caller) { - now = server.lua_time_snapshot; + if (server.script_caller) { + now = evalTimeSnapshot(); } /* If we are in the middle of a command execution, we still want to use * a reference time that does not change: in that case we just use the @@ -1743,6 +1744,11 @@ int evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult * return genericGetKeys(0, 2, 3, 1, argv, argc, result); } +int functionGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { + UNUSED(cmd); + return genericGetKeys(0, 2, 3, 1, argv, argc, result); +} + int lmpopGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { UNUSED(cmd); return genericGetKeys(0, 1, 2, 1, argv, argc, result); diff --git a/src/debug.c b/src/debug.c index 8776de38f..d4f3f5dd2 100644 --- a/src/debug.c +++ b/src/debug.c @@ -919,7 +919,7 @@ NULL addReplyStatus(c,"Apparently Redis did not crash: test passed"); } else if (!strcasecmp(c->argv[1]->ptr,"set-disable-deny-scripts") && c->argc == 3) { - server.lua_disable_deny_script = atoi(c->argv[2]->ptr);; + server.script_disable_deny_script = atoi(c->argv[2]->ptr);; addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"config-rewrite-force-all") && c->argc == 2) { diff --git a/src/defrag.c b/src/defrag.c index 734174c3a..ffd6eaba6 100644 --- a/src/defrag.c +++ b/src/defrag.c @@ -939,7 +939,7 @@ long defragOtherGlobals() { /* there are many more pointers to defrag (e.g. client argv, output / aof buffers, etc. * but we assume most of these are short lived, we only need to defrag allocations * that remain static for a long time */ - defragged += activeDefragSdsDict(server.lua_scripts, DEFRAG_SDS_DICT_VAL_IS_STROB); + defragged += activeDefragSdsDict(evalScriptsDict(), DEFRAG_SDS_DICT_VAL_IS_STROB); defragged += activeDefragSdsListAndDict(server.repl_scriptcache_fifo, server.repl_scriptcache_dict, DEFRAG_SDS_DICT_NO_VAL); defragged += moduleDefragGlobals(); return defragged; diff --git a/src/eval.c b/src/eval.c new file mode 100644 index 000000000..977cad721 --- /dev/null +++ b/src/eval.c @@ -0,0 +1,1636 @@ +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "server.h" +#include "sha1.h" +#include "rand.h" +#include "cluster.h" +#include "monotonic.h" +#include "resp_parser.h" +#include "script_lua.h" + +#include +#include +#include +#include +#include + +void ldbInit(void); +void ldbDisable(client *c); +void ldbEnable(client *c); +void evalGenericCommandWithDebugging(client *c, int evalsha); +sds ldbCatStackValue(sds s, lua_State *lua, int idx); + +/* Lua context */ +struct luaCtx { + lua_State *lua; /* The Lua interpreter. We use just one for all clients */ + client *lua_client; /* The "fake client" to query Redis from Lua */ + char *lua_cur_script; /* SHA1 of the script currently running, or NULL */ + dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ + unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */ + int lua_replicate_commands; /* True if we are doing single commands repl. */ +} lctx; + +/* Debugger shared state is stored inside this global structure. */ +#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */ +#define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */ +struct ldbState { + connection *conn; /* Connection of the debugging client. */ + int active; /* Are we debugging EVAL right now? */ + int forked; /* Is this a fork()ed debugging session? */ + list *logs; /* List of messages to send to the client. */ + list *traces; /* Messages about Redis commands executed since last stop.*/ + list *children; /* All forked debugging sessions pids. */ + int bp[LDB_BREAKPOINTS_MAX]; /* An array of breakpoints line numbers. */ + int bpcount; /* Number of valid entries inside bp. */ + int step; /* Stop at next line regardless of breakpoints. */ + int luabp; /* Stop at next line because redis.breakpoint() was called. */ + sds *src; /* Lua script source code split by line. */ + int lines; /* Number of lines in 'src'. */ + int currentline; /* Current line number. */ + sds cbuf; /* Debugger client command buffer. */ + size_t maxlen; /* Max var dump / reply length. */ + int maxlen_hint_sent; /* Did we already hint about "set maxlen"? */ +} ldb; + +/* --------------------------------------------------------------------------- + * Utility functions. + * ------------------------------------------------------------------------- */ + +/* Perform the SHA1 of the input string. We use this both for hashing script + * bodies in order to obtain the Lua function name, and in the implementation + * of redis.sha1(). + * + * 'digest' should point to a 41 bytes buffer: 40 for SHA1 converted into an + * hexadecimal number, plus 1 byte for null term. */ +void sha1hex(char *digest, char *script, size_t len) { + SHA1_CTX ctx; + unsigned char hash[20]; + char *cset = "0123456789abcdef"; + int j; + + SHA1Init(&ctx); + SHA1Update(&ctx,(unsigned char*)script,len); + SHA1Final(hash,&ctx); + + for (j = 0; j < 20; j++) { + digest[j*2] = cset[((hash[j]&0xF0)>>4)]; + digest[j*2+1] = cset[(hash[j]&0xF)]; + } + digest[40] = '\0'; +} + +/* redis.breakpoint() + * + * Allows to stop execution during a debugging session from within + * the Lua code implementation, like if a breakpoint was set in the code + * immediately after the function. */ +int luaRedisBreakpointCommand(lua_State *lua) { + if (ldb.active) { + ldb.luabp = 1; + lua_pushboolean(lua,1); + } else { + lua_pushboolean(lua,0); + } + return 1; +} + +/* redis.debug() + * + * Log a string message into the output console. + * Can take multiple arguments that will be separated by commas. + * Nothing is returned to the caller. */ +int luaRedisDebugCommand(lua_State *lua) { + if (!ldb.active) return 0; + int argc = lua_gettop(lua); + sds log = sdscatprintf(sdsempty()," line %d: ", ldb.currentline); + while(argc--) { + log = ldbCatStackValue(log,lua,-1 - argc); + if (argc != 0) log = sdscatlen(log,", ",2); + } + ldbLog(log); + return 0; +} + +/* redis.replicate_commands() + * + * Turn on single commands replication if the script never called + * a write command so far, and returns true. Otherwise if the script + * already started to write, returns false and stick to whole scripts + * replication, which is our default. */ +int luaRedisReplicateCommandsCommand(lua_State *lua) { + scriptRunCtx* rctx = luaGetFromRegistry(lua, REGISTRY_RUN_CTX_NAME); + if (rctx->flags & SCRIPT_WRITE_DIRTY) { + lua_pushboolean(lua,0); + } else { + lctx.lua_replicate_commands = 1; + rctx->flags &= ~SCRIPT_EVAL_REPLICATION; + /* When we switch to single commands replication, we can provide + * different math.random() sequences at every call, which is what + * the user normally expects. */ + redisSrand48(rand()); + lua_pushboolean(lua,1); + } + return 1; +} + +/* Initialize the scripting environment. + * + * This function is called the first time at server startup with + * the 'setup' argument set to 1. + * + * It can be called again multiple times during the lifetime of the Redis + * process, with 'setup' set to 0, and following a scriptingRelease() call, + * in order to reset the Lua scripting environment. + * + * However it is simpler to just call scriptingReset() that does just that. */ +void scriptingInit(int setup) { + lua_State *lua = lua_open(); + + if (setup) { + lctx.lua_client = NULL; + server.script_caller = NULL; + lctx.lua_cur_script = NULL; + server.script_disable_deny_script = 0; + ldbInit(); + } + + /* Initialize a dictionary we use to map SHAs to scripts. + * This is useful for replication, as we need to replicate EVALSHA + * as EVAL, so we need to remember the associated script. */ + lctx.lua_scripts = dictCreate(&shaScriptObjectDictType); + lctx.lua_scripts_mem = 0; + + luaRegisterRedisAPI(lua); + + /* register debug commands */ + lua_getglobal(lua,"redis"); + + /* redis.breakpoint */ + lua_pushstring(lua,"breakpoint"); + lua_pushcfunction(lua,luaRedisBreakpointCommand); + lua_settable(lua,-3); + + /* redis.debug */ + lua_pushstring(lua,"debug"); + lua_pushcfunction(lua,luaRedisDebugCommand); + lua_settable(lua,-3); + + /* redis.replicate_commands */ + lua_pushstring(lua, "replicate_commands"); + lua_pushcfunction(lua, luaRedisReplicateCommandsCommand); + lua_settable(lua, -3); + + lua_setglobal(lua,"redis"); + + /* Add a helper function that we use to sort the multi bulk output of non + * deterministic commands, when containing 'false' elements. */ + { + char *compare_func = "function __redis__compare_helper(a,b)\n" + " if a == false then a = '' end\n" + " if b == false then b = '' end\n" + " return aflags |= CLIENT_SCRIPT; + + /* We do not want to allow blocking commands inside Lua */ + lctx.lua_client->flags |= CLIENT_DENY_BLOCKING; + } + + /* Lua beginners often don't use "local", this is likely to introduce + * subtle bugs in their code. To prevent problems we protect accesses + * to global variables. */ + luaEnableGlobalsProtection(lua, 1); + + lctx.lua = lua; +} + +/* Release resources related to Lua scripting. + * This function is used in order to reset the scripting environment. */ +void scriptingRelease(int async) { + if (async) + freeLuaScriptsAsync(lctx.lua_scripts); + else + dictRelease(lctx.lua_scripts); + lctx.lua_scripts_mem = 0; + lua_close(lctx.lua); +} + +void scriptingReset(int async) { + scriptingRelease(async); + scriptingInit(0); +} + +/* --------------------------------------------------------------------------- + * EVAL and SCRIPT commands implementation + * ------------------------------------------------------------------------- */ + +/* Define a Lua function with the specified body. + * The function name will be generated in the following form: + * + * f_ + * + * The function increments the reference count of the 'body' object as a + * side effect of a successful call. + * + * On success a pointer to an SDS string representing the function SHA1 of the + * just added function is returned (and will be valid until the next call + * to scriptingReset() function), otherwise NULL is returned. + * + * The function handles the fact of being called with a script that already + * exists, and in such a case, it behaves like in the success case. + * + * If 'c' is not NULL, on error the client is informed with an appropriate + * error describing the nature of the problem and the Lua interpreter error. */ +sds luaCreateFunction(client *c, robj *body) { + char funcname[43]; + dictEntry *de; + + funcname[0] = 'f'; + funcname[1] = '_'; + sha1hex(funcname+2,body->ptr,sdslen(body->ptr)); + + sds sha = sdsnewlen(funcname+2,40); + if ((de = dictFind(lctx.lua_scripts,sha)) != NULL) { + sdsfree(sha); + return dictGetKey(de); + } + + sds funcdef = sdsempty(); + funcdef = sdscat(funcdef,"function "); + funcdef = sdscatlen(funcdef,funcname,42); + funcdef = sdscatlen(funcdef,"() ",3); + funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr)); + funcdef = sdscatlen(funcdef,"\nend",4); + + if (luaL_loadbuffer(lctx.lua,funcdef,sdslen(funcdef),"@user_script")) { + if (c != NULL) { + addReplyErrorFormat(c, + "Error compiling script (new function): %s\n", + lua_tostring(lctx.lua,-1)); + } + lua_pop(lctx.lua,1); + sdsfree(sha); + sdsfree(funcdef); + return NULL; + } + sdsfree(funcdef); + + if (lua_pcall(lctx.lua,0,0,0)) { + if (c != NULL) { + addReplyErrorFormat(c,"Error running script (new function): %s\n", + lua_tostring(lctx.lua,-1)); + } + lua_pop(lctx.lua,1); + sdsfree(sha); + return NULL; + } + + /* We also save a SHA1 -> Original script map in a dictionary + * so that we can replicate / write in the AOF all the + * EVALSHA commands as EVAL using the original script. */ + int retval = dictAdd(lctx.lua_scripts,sha,body); + serverAssertWithInfo(c ? c : lctx.lua_client,NULL,retval == DICT_OK); + lctx.lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body); + incrRefCount(body); + return sha; +} + +void prepareLuaClient(void) { + /* Select the right DB in the context of the Lua client */ + selectDb(lctx.lua_client,server.script_caller->db->id); + lctx.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */ + + /* If we are in MULTI context, flag Lua client as CLIENT_MULTI. */ + if (server.script_caller->flags & CLIENT_MULTI) { + lctx.lua_client->flags |= CLIENT_MULTI; + } +} + +void resetLuaClient(void) { + /* After the script done, remove the MULTI state. */ + lctx.lua_client->flags &= ~CLIENT_MULTI; +} + +void evalGenericCommand(client *c, int evalsha) { + lua_State *lua = lctx.lua; + char funcname[43]; + long long numkeys; + long long initial_server_dirty = server.dirty; + + /* When we replicate whole scripts, we want the same PRNG sequence at + * every call so that our PRNG is not affected by external state. */ + redisSrand48(0); + + lctx.lua_replicate_commands = server.lua_always_replicate_commands; + + /* Get the number of arguments that are keys */ + if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK) + return; + if (numkeys > (c->argc - 3)) { + addReplyError(c,"Number of keys can't be greater than number of args"); + return; + } else if (numkeys < 0) { + addReplyError(c,"Number of keys can't be negative"); + return; + } + + /* We obtain the script SHA1, then check if this function is already + * defined into the Lua state */ + funcname[0] = 'f'; + funcname[1] = '_'; + if (!evalsha) { + /* Hash the code if this is an EVAL call */ + sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr)); + } else { + /* We already have the SHA if it is an EVALSHA */ + int j; + char *sha = c->argv[1]->ptr; + + /* Convert to lowercase. We don't use tolower since the function + * managed to always show up in the profiler output consuming + * a non trivial amount of time. */ + for (j = 0; j < 40; j++) + funcname[j+2] = (sha[j] >= 'A' && sha[j] <= 'Z') ? + sha[j]+('a'-'A') : sha[j]; + funcname[42] = '\0'; + } + + /* Push the pcall error handler function on the stack. */ + lua_getglobal(lua, "__redis__err__handler"); + + /* Try to lookup the Lua function */ + lua_getglobal(lua, funcname); + if (lua_isnil(lua,-1)) { + lua_pop(lua,1); /* remove the nil from the stack */ + /* Function not defined... let's define it if we have the + * body of the function. If this is an EVALSHA call we can just + * return an error. */ + if (evalsha) { + lua_pop(lua,1); /* remove the error handler from the stack. */ + addReplyErrorObject(c, shared.noscripterr); + return; + } + if (luaCreateFunction(c,c->argv[1]) == NULL) { + lua_pop(lua,1); /* remove the error handler from the stack. */ + /* The error is sent to the client by luaCreateFunction() + * itself when it returns NULL. */ + return; + } + /* Now the following is guaranteed to return non nil */ + lua_getglobal(lua, funcname); + serverAssert(!lua_isnil(lua,-1)); + } + + lctx.lua_cur_script = funcname + 2; + + scriptRunCtx rctx; + scriptPrepareForRun(&rctx, lctx.lua_client, c, lctx.lua_cur_script); + rctx.flags |= SCRIPT_EVAL_MODE; /* mark the current run as legacy so we + will get legacy error messages and logs */ + if (!lctx.lua_replicate_commands) rctx.flags |= SCRIPT_EVAL_REPLICATION; + /* This check is for EVAL_RO, EVALSHA_RO. We want to allow only read only commands */ + if ((server.script_caller->cmd->proc == evalRoCommand || + server.script_caller->cmd->proc == evalShaRoCommand)) { + rctx.flags |= SCRIPT_READ_ONLY; + } + + luaCallFunction(&rctx, lua, c->argv+3, numkeys, c->argv+3+numkeys, c->argc-3-numkeys, ldb.active); + lua_pop(lua,1); /* Remove the error handler. */ + scriptResetRun(&rctx); + + lctx.lua_cur_script = NULL; + + /* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless + * we are sure that the script was already in the context of all the + * attached slaves *and* the current AOF file if enabled. + * + * To do so we use a cache of SHA1s of scripts that we already propagated + * as full EVAL, that's called the Replication Script Cache. + * + * For replication, every time a new slave attaches to the master, we need to + * flush our cache of scripts that can be replicated as EVALSHA, while + * for AOF we need to do so every time we rewrite the AOF file. */ + if (evalsha && !lctx.lua_replicate_commands) { + if (!replicationScriptCacheExists(c->argv[1]->ptr)) { + /* This script is not in our script cache, replicate it as + * EVAL, then add it into the script cache, as from now on + * slaves and AOF know about it. */ + robj *script = dictFetchValue(lctx.lua_scripts,c->argv[1]->ptr); + + replicationScriptCacheAdd(c->argv[1]->ptr); + serverAssertWithInfo(c,NULL,script != NULL); + + /* If the script did not produce any changes in the dataset we want + * just to replicate it as SCRIPT LOAD, otherwise we risk running + * an aborted script on slaves (that may then produce results there) + * or just running a CPU costly read-only script on the slaves. */ + if (server.dirty == initial_server_dirty) { + rewriteClientCommandVector(c,3, + shared.script, + shared.load, + script); + } else { + rewriteClientCommandArgument(c,0,shared.eval); + rewriteClientCommandArgument(c,1,script); + } + forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF); + } + } +} + +void evalCommand(client *c) { + /* Explicitly feed monitor here so that lua commands appear after their + * script command. */ + replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc); + if (!(c->flags & CLIENT_LUA_DEBUG)) + evalGenericCommand(c,0); + else + evalGenericCommandWithDebugging(c,0); +} + +void evalRoCommand(client *c) { + evalCommand(c); +} + +void evalShaCommand(client *c) { + /* Explicitly feed monitor here so that lua commands appear after their + * script command. */ + replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc); + if (sdslen(c->argv[1]->ptr) != 40) { + /* We know that a match is not possible if the provided SHA is + * not the right length. So we return an error ASAP, this way + * evalGenericCommand() can be implemented without string length + * sanity check */ + addReplyErrorObject(c, shared.noscripterr); + return; + } + if (!(c->flags & CLIENT_LUA_DEBUG)) + evalGenericCommand(c,1); + else { + addReplyError(c,"Please use EVAL instead of EVALSHA for debugging"); + return; + } +} + +void evalShaRoCommand(client *c) { + evalShaCommand(c); +} + +void scriptCommand(client *c) { + if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { + const char *help[] = { +"DEBUG (YES|SYNC|NO)", +" Set the debug mode for subsequent scripts executed.", +"EXISTS [ ...]", +" Return information about the existence of the scripts in the script cache.", +"FLUSH [ASYNC|SYNC]", +" Flush the Lua scripts cache. Very dangerous on replicas.", +" When called without the optional mode argument, the behavior is determined by the", +" lazyfree-lazy-user-flush configuration directive. Valid modes are:", +" * ASYNC: Asynchronously flush the scripts cache.", +" * SYNC: Synchronously flush the scripts cache.", +"KILL", +" Kill the currently executing Lua script.", +"LOAD