X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/c9edd1b28ae429e6c34462917fcb5c9d616e0ef8..89423052cab4700291f290819d53f4fbb8dabf95:/src/scripting.c diff --git a/src/scripting.c b/src/scripting.c index 44ceb1e2..6f9ec2e8 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -167,6 +167,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) { redisClient *c = server.lua_client; sds reply; + /* Require at least one argument */ + if (argc == 0) { + luaPushError(lua, + "Please specify at least one argument for redis.call()"); + return 1; + } + /* Build the arguments vector */ argv = zmalloc(sizeof(robj*)*argc); for (j = 0; j < argc; j++) { @@ -275,11 +282,10 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) { * reply as expected. */ if ((cmd->flags & REDIS_CMD_SORT_FOR_SCRIPT) && (reply[0] == '*' && reply[1] != '-')) { - /* Skip this step if command is SORT but output was already sorted */ - if (cmd->proc != sortCommand || server.sort_dontsort) luaSortArray(lua); } sdsfree(reply); + c->reply_bytes = 0; cleanup: /* Clean up. Command code may have changed argv/argc so we use the @@ -326,6 +332,34 @@ int luaRedisSha1hexCommand(lua_State *lua) { return 1; } +/* Returns a table with a single field 'field' set to the string value + * passed as argument. This helper function is handy when returning + * a Redis Protocol error or status reply from Lua: + * + * return redis.error_reply("ERR Some Error") + * return redis.status_reply("ERR Some Error") + */ +int luaRedisReturnSingleFieldTable(lua_State *lua, char *field) { + if (lua_gettop(lua) != 1 || lua_type(lua,-1) != LUA_TSTRING) { + luaPushError(lua, "wrong number or type of arguments"); + return 1; + } + + lua_newtable(lua); + lua_pushstring(lua, field); + lua_pushvalue(lua, -3); + lua_settable(lua, -3); + return 1; +} + +int luaRedisErrorReplyCommand(lua_State *lua) { + return luaRedisReturnSingleFieldTable(lua,"err"); +} + +int luaRedisStatusReplyCommand(lua_State *lua) { + return luaRedisReturnSingleFieldTable(lua,"ok"); +} + int luaLogCommand(lua_State *lua) { int j, argc = lua_gettop(lua); int level; @@ -412,14 +446,18 @@ void luaLoadLibraries(lua_State *lua) { #endif } +/* Remove a functions that we don't want to expose to the Redis scripting + * environment. */ +void luaRemoveUnsupportedFunctions(lua_State *lua) { + lua_pushnil(lua); + lua_setglobal(lua,"loadfile"); +} + /* This function installs metamethods in the global table _G that prevent * the creation of globals accidentally. * * It should be the last to be called in the scripting engine initialization - * sequence, because it may interact with creation of globals. - * Note that the function is designed to be called multiple times if needed - * without issues, because it is possible to enabled/disable globals protection - * at runtime with CONFIG SET. */ + * sequence, because it may interact with creation of globals. */ void scriptingEnableGlobalsProtection(lua_State *lua) { char *s[32]; sds code = sdsempty(); @@ -429,51 +467,38 @@ void scriptingEnableGlobalsProtection(lua_State *lua) { * Modified to be adapted to Redis. */ s[j++]="local mt = {}\n"; s[j++]="setmetatable(_G, mt)\n"; - s[j++]="mt.declared = {}\n"; s[j++]="mt.__newindex = function (t, n, v)\n"; - s[j++]=" if not mt.declared[n] and debug.getinfo(2) then\n"; + s[j++]=" if debug.getinfo(2) then\n"; s[j++]=" local w = debug.getinfo(2, \"S\").what\n"; s[j++]=" if w ~= \"main\" and w ~= \"C\" then\n"; - s[j++]=" error(\"assignment to undeclared global variable '\"..n..\"'\", 2)\n"; + s[j++]=" error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n"; s[j++]=" end\n"; - s[j++]=" mt.declared[n] = true\n"; s[j++]=" end\n"; s[j++]=" rawset(t, n, v)\n"; s[j++]="end\n"; s[j++]="mt.__index = function (t, n)\n"; - s[j++]=" if debug.getinfo(2) and not mt.declared[n] and debug.getinfo(2, \"S\").what ~= \"C\" then\n"; - s[j++]=" error(\"global variable '\"..n..\"' is not declared\", 2)\n"; + s[j++]=" if debug.getinfo(2) and debug.getinfo(2, \"S\").what ~= \"C\" then\n"; + s[j++]=" error(\"Script attempted to access unexisting global variable '\"..tostring(n)..\"'\", 2)\n"; s[j++]=" end\n"; s[j++]=" return rawget(t, n)\n"; s[j++]="end\n"; - s[j++]="function global(...)\n"; - s[j++]=" local nargs = select(\"#\",...)\n"; - s[j++]=" for i = 1, nargs do\n"; - s[j++]=" local v = select(i,...)\n"; - s[j++]=" mt.declared[v] = true\n"; - s[j++]=" end\n"; - s[j++]="end\n"; s[j++]=NULL; for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j])); - luaL_loadbuffer(lua,code,sdslen(code),"enable_strict_lua"); + luaL_loadbuffer(lua,code,sdslen(code),"@enable_strict_lua"); lua_pcall(lua,0,0,0); sdsfree(code); } -void scriptingDisableGlobalsProtection(lua_State *lua) { - char *s = "setmetatable(_G, nil)\n"; - luaL_loadbuffer(lua,s,strlen(s),"disable_strict_lua"); - lua_pcall(lua,0,0,0); -} - /* Initialize the scripting environment. * It is possible to call this function to reset the scripting environment * assuming that we call scriptingRelease() before. * See scriptingReset() for more information. */ void scriptingInit(void) { lua_State *lua = lua_open(); + luaLoadLibraries(lua); + luaRemoveUnsupportedFunctions(lua); /* Initialize a dictionary we use to map SHAs to scripts. * This is useful for replication, as we need to replicate EVALSHA @@ -519,6 +544,14 @@ void scriptingInit(void) { lua_pushcfunction(lua, luaRedisSha1hexCommand); lua_settable(lua, -3); + /* redis.error_reply and redis.status_reply */ + lua_pushstring(lua, "error_reply"); + lua_pushcfunction(lua, luaRedisErrorReplyCommand); + lua_settable(lua, -3); + lua_pushstring(lua, "status_reply"); + lua_pushcfunction(lua, luaRedisStatusReplyCommand); + lua_settable(lua, -3); + /* Finally set the table as 'redis' global var. */ lua_setglobal(lua,"redis"); @@ -543,7 +576,7 @@ void scriptingInit(void) { " if b == false then b = '' end\n" " return aptr,sdslen(body->ptr)); funcdef = sdscatlen(funcdef," end",4); - if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"func definition")) { + if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) { addReplyErrorFormat(c,"Error compiling script (new function): %s\n", lua_tostring(lua,-1)); lua_pop(lua,1); @@ -726,6 +758,7 @@ void evalGenericCommand(redisClient *c, int evalsha) { lua_State *lua = server.lua; char funcname[43]; long long numkeys; + int delhook = 0; /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. */ @@ -796,19 +829,19 @@ void evalGenericCommand(redisClient *c, int evalsha) { * is running for too much time. * We set the hook only if the time limit is enabled as the hook will * make the Lua script execution slower. */ + server.lua_caller = c; + server.lua_time_start = ustime()/1000; + server.lua_kill = 0; if (server.lua_time_limit > 0 && server.masterhost == NULL) { lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); - } else { - lua_sethook(lua,luaMaskCountHook,0,0); + delhook = 1; } /* At this point whatever this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ - server.lua_caller = c; - server.lua_time_start = ustime()/1000; - server.lua_kill = 0; if (lua_pcall(lua,0,1,0)) { + if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ if (server.lua_timedout) { server.lua_timedout = 0; /* Restore the readable handler that was unregistered when the @@ -824,6 +857,7 @@ void evalGenericCommand(redisClient *c, int evalsha) { lua_gc(lua,LUA_GCCOLLECT,0); return; } + if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ server.lua_timedout = 0; server.lua_caller = NULL; selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ @@ -943,9 +977,9 @@ void scriptCommand(redisClient *c) { sdsfree(sha); } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) { if (server.lua_caller == NULL) { - addReplyError(c,"No scripts in execution right now."); + addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n")); } else if (server.lua_write_dirty) { - addReplyError(c, "Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in an hard way using the SHUTDOWN NOSAVE command."); + addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in an hard way using the SHUTDOWN NOSAVE command.\r\n")); } else { server.lua_kill = 1; addReply(c,shared.ok);