summaryrefslogtreecommitdiff
path: root/src/scripting.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/scripting.c')
-rw-r--r--src/scripting.c293
1 files changed, 252 insertions, 41 deletions
diff --git a/src/scripting.c b/src/scripting.c
index cbbf43fb1..7cf21f408 100644
--- a/src/scripting.c
+++ b/src/scripting.c
@@ -42,7 +42,10 @@ char *redisProtocolToLuaType_Int(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Error(lua_State *lua, char *reply);
-char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype);
+char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype);
+char *redisProtocolToLuaType_Null(lua_State *lua, char *reply);
+char *redisProtocolToLuaType_Bool(lua_State *lua, char *reply, int tf);
+char *redisProtocolToLuaType_Double(lua_State *lua, char *reply);
int redis_math_random (lua_State *L);
int redis_math_randomseed (lua_State *L);
void ldbInit(void);
@@ -58,7 +61,7 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx);
#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
#define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */
struct ldbState {
- int fd; /* Socket of the debugging client. */
+ 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. */
@@ -132,9 +135,12 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break;
case '+': p = redisProtocolToLuaType_Status(lua,reply); break;
case '-': p = redisProtocolToLuaType_Error(lua,reply); break;
- case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
- case '%': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
- case '~': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
+ case '*': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
+ case '%': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
+ case '~': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
+ case '_': p = redisProtocolToLuaType_Null(lua,reply); break;
+ case '#': p = redisProtocolToLuaType_Bool(lua,reply,p[1]); break;
+ case ',': p = redisProtocolToLuaType_Double(lua,reply); break;
}
return p;
}
@@ -182,13 +188,13 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply) {
return p+2;
}
-char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
+char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype) {
char *p = strchr(reply+1,'\r');
long long mbulklen;
int j = 0;
string2ll(reply+1,p-reply-1,&mbulklen);
- if (server.lua_caller->resp == 2 || atype == '*') {
+ if (server.lua_client->resp == 2 || atype == '*') {
p += 2;
if (mbulklen == -1) {
lua_pushboolean(lua,0);
@@ -200,11 +206,15 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
p = redisProtocolToLuaType(lua,p);
lua_settable(lua,-3);
}
- } else if (server.lua_caller->resp == 3) {
+ } else if (server.lua_client->resp == 3) {
/* Here we handle only Set and Map replies in RESP3 mode, since arrays
- * follow the above RESP2 code path. */
+ * follow the above RESP2 code path. Note that those are represented
+ * as a table with the "map" or "set" field populated with the actual
+ * table representing the set or the map type. */
p += 2;
lua_newtable(lua);
+ lua_pushstring(lua,atype == '%' ? "map" : "set");
+ lua_newtable(lua);
for (j = 0; j < mbulklen; j++) {
p = redisProtocolToLuaType(lua,p);
if (atype == '%') {
@@ -214,10 +224,44 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
}
lua_settable(lua,-3);
}
+ lua_settable(lua,-3);
}
return p;
}
+char *redisProtocolToLuaType_Null(lua_State *lua, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ lua_pushnil(lua);
+ return p+2;
+}
+
+char *redisProtocolToLuaType_Bool(lua_State *lua, char *reply, int tf) {
+ char *p = strchr(reply+1,'\r');
+ lua_pushboolean(lua,tf == 't');
+ return p+2;
+}
+
+char *redisProtocolToLuaType_Double(lua_State *lua, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ char buf[MAX_LONG_DOUBLE_CHARS+1];
+ size_t len = p-reply-1;
+ double d;
+
+ if (len <= MAX_LONG_DOUBLE_CHARS) {
+ memcpy(buf,reply+1,len);
+ buf[len] = '\0';
+ d = strtod(buf,NULL); /* We expect a valid representation. */
+ } else {
+ d = 0;
+ }
+
+ lua_newtable(lua);
+ lua_pushstring(lua,"double");
+ lua_pushnumber(lua,d);
+ lua_settable(lua,-3);
+ return p+2;
+}
+
/* This function is used in order to push an error on the Lua stack in the
* format used by redis.pcall to return errors, which is a lua table
* with a single "err" field set to the error string. Note that this
@@ -292,6 +336,8 @@ void luaSortArray(lua_State *lua) {
* Lua reply to Redis reply conversion functions.
* ------------------------------------------------------------------------- */
+/* Reply to client 'c' converting the top element in the Lua stack to a
+ * Redis reply. As a side effect the element is consumed from the stack. */
void luaReplyToRedisReply(client *c, lua_State *lua) {
int t = lua_type(lua,-1);
@@ -300,7 +346,11 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
break;
case LUA_TBOOLEAN:
- addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.null[c->resp]);
+ if (server.lua_client->resp == 2)
+ addReply(c,lua_toboolean(lua,-1) ? shared.cone :
+ shared.null[c->resp]);
+ else
+ addReplyBool(c,lua_toboolean(lua,-1));
break;
case LUA_TNUMBER:
addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
@@ -310,6 +360,8 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
* Error are returned as a single element table with 'err' field.
* Status replies are returned as single element table with 'ok'
* field. */
+
+ /* Handle error reply. */
lua_pushstring(lua,"err");
lua_gettable(lua,-2);
t = lua_type(lua,-1);
@@ -321,8 +373,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
lua_pop(lua,2);
return;
}
+ lua_pop(lua,1); /* Discard field name pushed before. */
- lua_pop(lua,1);
+ /* Handle status reply. */
lua_pushstring(lua,"ok");
lua_gettable(lua,-2);
t = lua_type(lua,-1);
@@ -331,25 +384,81 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
sdsmapchars(ok,"\r\n"," ",2);
addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok));
sdsfree(ok);
- lua_pop(lua,1);
- } else {
+ lua_pop(lua,2);
+ return;
+ }
+ lua_pop(lua,1); /* Discard field name pushed before. */
+
+ /* Handle double reply. */
+ lua_pushstring(lua,"double");
+ lua_gettable(lua,-2);
+ t = lua_type(lua,-1);
+ if (t == LUA_TNUMBER) {
+ addReplyDouble(c,lua_tonumber(lua,-1));
+ lua_pop(lua,2);
+ return;
+ }
+ lua_pop(lua,1); /* Discard field name pushed before. */
+
+ /* Handle map reply. */
+ lua_pushstring(lua,"map");
+ lua_gettable(lua,-2);
+ t = lua_type(lua,-1);
+ if (t == LUA_TTABLE) {
+ int maplen = 0;
void *replylen = addReplyDeferredLen(c);
- int j = 1, mbulklen = 0;
-
- lua_pop(lua,1); /* Discard the 'ok' field value we popped */
- while(1) {
- lua_pushnumber(lua,j++);
- lua_gettable(lua,-2);
- t = lua_type(lua,-1);
- if (t == LUA_TNIL) {
- lua_pop(lua,1);
- break;
- }
- luaReplyToRedisReply(c, lua);
- mbulklen++;
+ lua_pushnil(lua); /* Use nil to start iteration. */
+ while (lua_next(lua,-2)) {
+ /* Stack now: table, key, value */
+ luaReplyToRedisReply(c, lua); /* Return value. */
+ lua_pushvalue(lua,-1); /* Dup key before consuming. */
+ luaReplyToRedisReply(c, lua); /* Return key. */
+ /* Stack now: table, key. */
+ maplen++;
}
- setDeferredArrayLen(c,replylen,mbulklen);
+ setDeferredMapLen(c,replylen,maplen);
+ lua_pop(lua,2);
+ return;
}
+ lua_pop(lua,1); /* Discard field name pushed before. */
+
+ /* Handle set reply. */
+ lua_pushstring(lua,"set");
+ lua_gettable(lua,-2);
+ t = lua_type(lua,-1);
+ if (t == LUA_TTABLE) {
+ int setlen = 0;
+ void *replylen = addReplyDeferredLen(c);
+ lua_pushnil(lua); /* Use nil to start iteration. */
+ while (lua_next(lua,-2)) {
+ /* Stack now: table, key, true */
+ lua_pop(lua,1); /* Discard the boolean value. */
+ lua_pushvalue(lua,-1); /* Dup key before consuming. */
+ luaReplyToRedisReply(c, lua); /* Return key. */
+ /* Stack now: table, key. */
+ setlen++;
+ }
+ setDeferredSetLen(c,replylen,setlen);
+ lua_pop(lua,2);
+ return;
+ }
+ lua_pop(lua,1); /* Discard field name pushed before. */
+
+ /* Handle the array reply. */
+ void *replylen = addReplyDeferredLen(c);
+ int j = 1, mbulklen = 0;
+ while(1) {
+ lua_pushnumber(lua,j++);
+ lua_gettable(lua,-2);
+ t = lua_type(lua,-1);
+ if (t == LUA_TNIL) {
+ lua_pop(lua,1);
+ break;
+ }
+ luaReplyToRedisReply(c, lua);
+ mbulklen++;
+ }
+ setDeferredArrayLen(c,replylen,mbulklen);
break;
default:
addReplyNull(c);
@@ -462,6 +571,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
c->argc = argc;
c->user = server.lua_caller->user;
+ /* Process module hooks */
+ moduleCallCommandFilters(c);
+ argv = c->argv;
+ argc = c->argc;
+
/* Log the command if debugging is active. */
if (ldb.active && ldb.step) {
sds cmdlog = sdsnew("<redis>");
@@ -854,6 +968,25 @@ int luaLogCommand(lua_State *lua) {
return 0;
}
+/* redis.setresp() */
+int luaSetResp(lua_State *lua) {
+ int argc = lua_gettop(lua);
+
+ if (argc != 1) {
+ lua_pushstring(lua, "redis.setresp() requires one argument.");
+ return lua_error(lua);
+ }
+
+ int resp = lua_tonumber(lua,-argc);
+ if (resp != 2 && resp != 3) {
+ lua_pushstring(lua, "RESP version must be 2 or 3.");
+ return lua_error(lua);
+ }
+
+ server.lua_client->resp = resp;
+ return 0;
+}
+
/* ---------------------------------------------------------------------------
* Lua engine initialization and reset.
* ------------------------------------------------------------------------- */
@@ -950,6 +1083,7 @@ void scriptingInit(int setup) {
if (setup) {
server.lua_client = NULL;
server.lua_caller = NULL;
+ server.lua_cur_script = NULL;
server.lua_timedout = 0;
ldbInit();
}
@@ -981,6 +1115,11 @@ void scriptingInit(int setup) {
lua_pushcfunction(lua,luaLogCommand);
lua_settable(lua,-3);
+ /* redis.setresp */
+ lua_pushstring(lua,"setresp");
+ lua_pushcfunction(lua,luaSetResp);
+ lua_settable(lua,-3);
+
lua_pushstring(lua,"LOG_DEBUG");
lua_pushnumber(lua,LL_DEBUG);
lua_settable(lua,-3);
@@ -1104,7 +1243,7 @@ void scriptingInit(int setup) {
* Note: there is no need to create it again when this function is called
* by scriptingReset(). */
if (server.lua_client == NULL) {
- server.lua_client = createClient(-1);
+ server.lua_client = createClient(NULL);
server.lua_client->flags |= CLIENT_LUA;
}
@@ -1269,7 +1408,11 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
/* Set the timeout condition if not already set and the maximum
* execution time was reached. */
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
- serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
+ serverLog(LL_WARNING,
+ "Lua slow script detected: still in execution after %lld milliseconds. "
+ "You can try killing the script using the SCRIPT KILL command. "
+ "Script SHA1 is: %s",
+ elapsed, server.lua_cur_script);
server.lua_timedout = 1;
/* Once the script timeouts we reenter the event loop to permit others
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
@@ -1374,8 +1517,9 @@ void evalGenericCommand(client *c, int evalsha) {
luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys);
luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
- /* Select the right DB in the context of the Lua client */
+ /* Set the Lua client database and protocol. */
selectDb(server.lua_client,c->db->id);
+ server.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */
/* Set a hook in order to be able to stop the script execution if it
* is running for too much time.
@@ -1385,6 +1529,7 @@ void evalGenericCommand(client *c, int evalsha) {
* If we are debugging, we set instead a "line" hook so that the
* debugger is call-back at every line executed by the script. */
server.lua_caller = c;
+ server.lua_cur_script = funcname + 2;
server.lua_time_start = mstime();
server.lua_kill = 0;
if (server.lua_time_limit > 0 && ldb.active == 0) {
@@ -1411,6 +1556,7 @@ void evalGenericCommand(client *c, int evalsha) {
queueClientForReprocessing(server.master);
}
server.lua_caller = NULL;
+ server.lua_cur_script = NULL;
/* Call the Lua garbage collector from time to time to avoid a
* full cycle performed by Lua, which adds too latency.
@@ -1588,7 +1734,7 @@ NULL
/* Initialize Lua debugger data structures. */
void ldbInit(void) {
- ldb.fd = -1;
+ ldb.conn = NULL;
ldb.active = 0;
ldb.logs = listCreate();
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
@@ -1610,7 +1756,7 @@ void ldbFlushLog(list *log) {
void ldbEnable(client *c) {
c->flags |= CLIENT_LUA_DEBUG;
ldbFlushLog(ldb.logs);
- ldb.fd = c->fd;
+ ldb.conn = c->conn;
ldb.step = 1;
ldb.bpcount = 0;
ldb.luabp = 0;
@@ -1665,7 +1811,7 @@ void ldbSendLogs(void) {
proto = sdscatlen(proto,"\r\n",2);
listDelNode(ldb.logs,ln);
}
- if (write(ldb.fd,proto,sdslen(proto)) == -1) {
+ if (connWrite(ldb.conn,proto,sdslen(proto)) == -1) {
/* Avoid warning. We don't check the return value of write()
* since the next read() will catch the I/O error and will
* close the debugging session. */
@@ -1688,7 +1834,7 @@ void ldbSendLogs(void) {
int ldbStartSession(client *c) {
ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0;
if (ldb.forked) {
- pid_t cp = fork();
+ pid_t cp = redisFork();
if (cp == -1) {
addReplyError(c,"Fork() failed: can't run EVAL in debugging mode.");
return 0;
@@ -1705,7 +1851,6 @@ int ldbStartSession(client *c) {
* socket to make sure if the parent crashes a reset is sent
* to the clients. */
serverLog(LL_WARNING,"Redis forked for debugging eval");
- closeListeningSockets(0);
} else {
/* Parent */
listAddNodeTail(ldb.children,(void*)(unsigned long)cp);
@@ -1718,8 +1863,8 @@ int ldbStartSession(client *c) {
}
/* Setup our debugging session. */
- anetBlock(NULL,ldb.fd);
- anetSendTimeout(NULL,ldb.fd,5000);
+ connBlock(ldb.conn);
+ connSendTimeout(ldb.conn,5000);
ldb.active = 1;
/* First argument of EVAL is the script itself. We split it into different
@@ -1746,7 +1891,7 @@ void ldbEndSession(client *c) {
/* If it's a fork()ed session, we just exit. */
if (ldb.forked) {
- writeToClient(c->fd, c, 0);
+ writeToClient(c,0);
serverLog(LL_WARNING,"Lua debugging session child exiting");
exitFromChild(0);
} else {
@@ -1755,8 +1900,8 @@ void ldbEndSession(client *c) {
}
/* Otherwise let's restore client's state. */
- anetNonBlock(NULL,ldb.fd);
- anetSendTimeout(NULL,ldb.fd,0);
+ connNonBlock(ldb.conn);
+ connSendTimeout(ldb.conn,0);
/* Close the client connectin after sending the final EVAL reply
* in order to signal the end of the debugging session. */
@@ -2048,6 +2193,11 @@ char *ldbRedisProtocolToHuman_Int(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Status(sds *o, char *reply);
char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply);
+char *ldbRedisProtocolToHuman_Set(sds *o, char *reply);
+char *ldbRedisProtocolToHuman_Map(sds *o, char *reply);
+char *ldbRedisProtocolToHuman_Null(sds *o, char *reply);
+char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply);
+char *ldbRedisProtocolToHuman_Double(sds *o, char *reply);
/* Get Redis protocol from 'reply' and appends it in human readable form to
* the passed SDS string 'o'.
@@ -2062,6 +2212,11 @@ char *ldbRedisProtocolToHuman(sds *o, char *reply) {
case '+': p = ldbRedisProtocolToHuman_Status(o,reply); break;
case '-': p = ldbRedisProtocolToHuman_Status(o,reply); break;
case '*': p = ldbRedisProtocolToHuman_MultiBulk(o,reply); break;
+ case '~': p = ldbRedisProtocolToHuman_Set(o,reply); break;
+ case '%': p = ldbRedisProtocolToHuman_Map(o,reply); break;
+ case '_': p = ldbRedisProtocolToHuman_Null(o,reply); break;
+ case '#': p = ldbRedisProtocolToHuman_Bool(o,reply); break;
+ case ',': p = ldbRedisProtocolToHuman_Double(o,reply); break;
}
return p;
}
@@ -2116,6 +2271,62 @@ char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply) {
return p;
}
+char *ldbRedisProtocolToHuman_Set(sds *o, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ long long mbulklen;
+ int j = 0;
+
+ string2ll(reply+1,p-reply-1,&mbulklen);
+ p += 2;
+ *o = sdscatlen(*o,"~(",2);
+ for (j = 0; j < mbulklen; j++) {
+ p = ldbRedisProtocolToHuman(o,p);
+ if (j != mbulklen-1) *o = sdscatlen(*o,",",1);
+ }
+ *o = sdscatlen(*o,")",1);
+ return p;
+}
+
+char *ldbRedisProtocolToHuman_Map(sds *o, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ long long mbulklen;
+ int j = 0;
+
+ string2ll(reply+1,p-reply-1,&mbulklen);
+ p += 2;
+ *o = sdscatlen(*o,"{",1);
+ for (j = 0; j < mbulklen; j++) {
+ p = ldbRedisProtocolToHuman(o,p);
+ *o = sdscatlen(*o," => ",4);
+ p = ldbRedisProtocolToHuman(o,p);
+ if (j != mbulklen-1) *o = sdscatlen(*o,",",1);
+ }
+ *o = sdscatlen(*o,"}",1);
+ return p;
+}
+
+char *ldbRedisProtocolToHuman_Null(sds *o, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ *o = sdscatlen(*o,"(null)",6);
+ return p+2;
+}
+
+char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ if (reply[1] == 't')
+ *o = sdscatlen(*o,"#true",5);
+ else
+ *o = sdscatlen(*o,"#false",6);
+ return p+2;
+}
+
+char *ldbRedisProtocolToHuman_Double(sds *o, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ *o = sdscatlen(*o,"(double) ",9);
+ *o = sdscatlen(*o,reply+1,p-reply-1);
+ return p+2;
+}
+
/* Log a Redis reply as debugger output, in an human readable format.
* If the resulting string is longer than 'len' plus a few more chars
* used as prefix, it gets truncated. */
@@ -2327,7 +2538,7 @@ int ldbRepl(lua_State *lua) {
while(1) {
while((argv = ldbReplParseCommand(&argc)) == NULL) {
char buf[1024];
- int nread = read(ldb.fd,buf,sizeof(buf));
+ int nread = connRead(ldb.conn,buf,sizeof(buf));
if (nread <= 0) {
/* Make sure the script runs without user input since the
* client is no longer connected. */