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++) {
* 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
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;
#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();
* 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
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");
" if b == false then b = '' end\n"
" return a<b\n"
"end\n";
- luaL_loadbuffer(lua,compare_func,strlen(compare_func),"cmp_func_def");
+ luaL_loadbuffer(lua,compare_func,strlen(compare_func),"@cmp_func_def");
lua_pcall(lua,0,0,0);
}
/* Lua beginners ofter don't use "local", this is likely to introduce
* subtle bugs in their code. To prevent problems we protect accesses
* to global variables. */
- if (server.lua_protect_globals)
- scriptingEnableGlobalsProtection(lua);
+ scriptingEnableGlobalsProtection(lua);
server.lua = lua;
}
funcdef = sdscatlen(funcdef,body->ptr,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);
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. */
* 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
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 */
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);