2012-11-08 12:25:23 -05:00
/*
* Copyright ( c ) 2009 - 2012 , Salvatore Sanfilippo < antirez at gmail dot com >
* 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 .
*/
2015-07-26 09:14:57 -04:00
# include "server.h"
2011-04-30 11:46:52 -04:00
# include "sha1.h"
2011-09-23 09:40:58 -04:00
# include "rand.h"
2015-01-09 04:39:05 -05:00
# include "cluster.h"
2011-04-30 11:46:52 -04:00
# include <lua.h>
# include <lauxlib.h>
# include <lualib.h>
2011-05-13 16:02:38 -04:00
# include <ctype.h>
2011-09-23 09:40:58 -04:00
# include <math.h>
2011-04-30 11:46:52 -04:00
2011-05-01 08:47:52 -04:00
char * redisProtocolToLuaType_Int ( lua_State * lua , char * reply ) ;
char * redisProtocolToLuaType_Bulk ( lua_State * lua , char * reply ) ;
char * redisProtocolToLuaType_Status ( lua_State * lua , char * reply ) ;
2011-05-01 09:26:47 -04:00
char * redisProtocolToLuaType_Error ( lua_State * lua , char * reply ) ;
char * redisProtocolToLuaType_MultiBulk ( lua_State * lua , char * reply ) ;
2011-09-23 09:40:58 -04:00
int redis_math_random ( lua_State * L ) ;
int redis_math_randomseed ( lua_State * L ) ;
2015-11-06 10:19:59 -05:00
void ldbInit ( void ) ;
void ldbDisable ( client * c ) ;
void ldbEnable ( client * c ) ;
void evalGenericCommandWithDebugging ( client * c , int evalsha ) ;
void luaLdbLineHook ( lua_State * lua , lua_Debug * ar ) ;
2015-11-09 11:01:41 -05:00
void ldbLog ( sds entry ) ;
2015-11-11 04:15:26 -05:00
void ldbLogRedisReply ( char * reply , size_t maxlen ) ;
2015-11-12 02:33:52 -05:00
sds ldbCatStackValue ( sds s , lua_State * lua , int idx ) ;
2015-11-06 10:19:59 -05:00
/* Debugger shared state is stored inside this global structure. */
2015-11-11 04:15:26 -05:00
# define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
# define LDB_REPLY_MAX_LOG_LEN 60 /* Max chars when logging a reply. */
2015-11-06 10:19:59 -05:00
struct ldbState {
int fd ; /* Socket 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.*/
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 ragardless of breakpoints. */
2015-11-11 04:28:35 -05:00
int luabp ; /* Stop at next line because redis.breakpoint() was called. */
2015-11-09 05:08:57 -05:00
sds * src ; /* Lua script source code split by line. */
int lines ; /* Number of lines in 'src'. */
2015-11-09 11:01:41 -05:00
int currentline ; /* Current line number. */
2015-11-09 05:08:57 -05:00
sds cbuf ; /* Debugger client command buffer. */
2015-11-06 10:19:59 -05:00
} ldb ;
2015-11-05 04:36:52 -05:00
/* ---------------------------------------------------------------------------
* 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 reply to Lua type conversion functions .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2011-05-01 08:47:52 -04:00
/* Take a Redis reply in the Redis protocol format and convert it into a
* Lua type . Thanks to this function , and the introduction of not connected
2013-01-16 12:00:20 -05:00
* clients , it is trivial to implement the redis ( ) lua function .
2011-05-01 08:47:52 -04:00
*
* Basically we take the arguments , execute the Redis command in the context
* of a non connected client , then take the generated reply and convert it
* into a suitable Lua type . With this trick the scripting feature does not
2015-11-11 04:15:26 -05:00
* need the introduction of a full Redis internals API . The script
2011-05-01 08:47:52 -04:00
* is like a normal client that bypasses all the slow I / O paths .
*
* Note : in this function we do not do any sanity check as the reply is
2012-01-31 10:09:21 -05:00
* generated by Redis directly . This allows us to go faster .
2011-05-01 08:47:52 -04:00
*
* Errors are returned as a table with a single ' err ' field set to the
* error string .
*/
char * redisProtocolToLuaType ( lua_State * lua , char * reply ) {
char * p = reply ;
switch ( * p ) {
2015-11-11 04:15:26 -05:00
case ' : ' : p = redisProtocolToLuaType_Int ( lua , reply ) ; break ;
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 ) ; break ;
2011-05-01 08:47:52 -04:00
}
return p ;
}
char * redisProtocolToLuaType_Int ( lua_State * lua , char * reply ) {
char * p = strchr ( reply + 1 , ' \r ' ) ;
long long value ;
string2ll ( reply + 1 , p - reply - 1 , & value ) ;
lua_pushnumber ( lua , ( lua_Number ) value ) ;
return p + 2 ;
}
char * redisProtocolToLuaType_Bulk ( lua_State * lua , char * reply ) {
char * p = strchr ( reply + 1 , ' \r ' ) ;
long long bulklen ;
string2ll ( reply + 1 , p - reply - 1 , & bulklen ) ;
2011-05-01 17:43:10 -04:00
if ( bulklen = = - 1 ) {
2011-05-13 10:42:43 -04:00
lua_pushboolean ( lua , 0 ) ;
2011-05-01 08:47:52 -04:00
return p + 2 ;
} else {
lua_pushlstring ( lua , p + 2 , bulklen ) ;
return p + 2 + bulklen + 2 ;
}
}
char * redisProtocolToLuaType_Status ( lua_State * lua , char * reply ) {
char * p = strchr ( reply + 1 , ' \r ' ) ;
2011-05-02 04:08:26 -04:00
lua_newtable ( lua ) ;
lua_pushstring ( lua , " ok " ) ;
2011-05-01 08:47:52 -04:00
lua_pushlstring ( lua , reply + 1 , p - reply - 1 ) ;
2011-05-02 04:08:26 -04:00
lua_settable ( lua , - 3 ) ;
2011-05-01 08:47:52 -04:00
return p + 2 ;
}
2011-05-01 09:26:47 -04:00
char * redisProtocolToLuaType_Error ( lua_State * lua , char * reply ) {
char * p = strchr ( reply + 1 , ' \r ' ) ;
lua_newtable ( lua ) ;
lua_pushstring ( lua , " err " ) ;
lua_pushlstring ( lua , reply + 1 , p - reply - 1 ) ;
lua_settable ( lua , - 3 ) ;
return p + 2 ;
}
char * redisProtocolToLuaType_MultiBulk ( lua_State * lua , char * reply ) {
char * p = strchr ( reply + 1 , ' \r ' ) ;
long long mbulklen ;
int j = 0 ;
string2ll ( reply + 1 , p - reply - 1 , & mbulklen ) ;
p + = 2 ;
if ( mbulklen = = - 1 ) {
2011-05-13 10:42:43 -04:00
lua_pushboolean ( lua , 0 ) ;
2011-05-01 09:26:47 -04:00
return p ;
}
lua_newtable ( lua ) ;
for ( j = 0 ; j < mbulklen ; j + + ) {
2011-05-01 09:48:26 -04:00
lua_pushnumber ( lua , j + 1 ) ;
2011-05-01 09:26:47 -04:00
p = redisProtocolToLuaType ( lua , p ) ;
lua_settable ( lua , - 3 ) ;
}
return p ;
}
2015-11-09 05:43:18 -05:00
/* 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
* table is never a valid reply by proper commands , since the returned
* tables are otherwise always indexed by integers , never by strings . */
2011-05-01 17:43:10 -04:00
void luaPushError ( lua_State * lua , char * error ) {
2013-05-22 19:17:58 -04:00
lua_Debug dbg ;
2015-11-09 11:01:41 -05:00
/* If debugging is active and in step mode, log errors resulting from
* Redis commands . */
if ( ldb . active & & ldb . step ) {
ldbLog ( sdscatprintf ( sdsempty ( ) , " <error> %s " , error ) ) ;
}
2011-05-01 17:43:10 -04:00
lua_newtable ( lua ) ;
lua_pushstring ( lua , " err " ) ;
2013-05-22 19:17:58 -04:00
/* Attempt to figure out where this function was called, if possible */
if ( lua_getstack ( lua , 1 , & dbg ) & & lua_getinfo ( lua , " nSl " , & dbg ) ) {
sds msg = sdscatprintf ( sdsempty ( ) , " %s: %d: %s " ,
dbg . source , dbg . currentline , error ) ;
lua_pushstring ( lua , msg ) ;
sdsfree ( msg ) ;
} else {
lua_pushstring ( lua , error ) ;
}
2011-05-01 17:43:10 -04:00
lua_settable ( lua , - 3 ) ;
}
2015-11-09 05:43:18 -05:00
/* In case the error set into the Lua stack by luaPushError() was generated
* by the non - error - trapping version of redis . pcall ( ) , which is redis . call ( ) ,
* this function will raise the Lua error so that the execution of the
* script will be halted . */
int luaRaiseError ( lua_State * lua ) {
lua_pushstring ( lua , " err " ) ;
lua_gettable ( lua , - 2 ) ;
return lua_error ( lua ) ;
}
2012-01-31 10:09:21 -05:00
/* Sort the array currently in the stack. We do this to make the output
* of commands like KEYS or SMEMBERS something deterministic when called
* from Lua ( to play well with AOf / replication ) .
*
* The array is sorted using table . sort itself , and assuming all the
* list elements are strings . */
void luaSortArray ( lua_State * lua ) {
/* Initial Stack: array */
lua_getglobal ( lua , " table " ) ;
lua_pushstring ( lua , " sort " ) ;
lua_gettable ( lua , - 2 ) ; /* Stack: array, table, table.sort */
lua_pushvalue ( lua , - 3 ) ; /* Stack: array, table, table.sort, array */
2012-02-01 09:22:28 -05:00
if ( lua_pcall ( lua , 1 , 0 , 0 ) ) {
/* Stack: array, table, error */
/* We are not interested in the error, we assume that the problem is
* that there are ' false ' elements inside the array , so we try
* again with a slower function but able to handle this case , that
* is : table . sort ( table , __redis__compare_helper ) */
lua_pop ( lua , 1 ) ; /* Stack: array, table */
lua_pushstring ( lua , " sort " ) ; /* Stack: array, table, sort */
lua_gettable ( lua , - 2 ) ; /* Stack: array, table, table.sort */
lua_pushvalue ( lua , - 3 ) ; /* Stack: array, table, table.sort, array */
lua_getglobal ( lua , " __redis__compare_helper " ) ;
/* Stack: array, table, table.sort, array, __redis__compare_helper */
lua_call ( lua , 2 , 0 ) ;
}
/* Stack: array (sorted), table */
2012-01-31 10:09:21 -05:00
lua_pop ( lua , 1 ) ; /* Stack: array (sorted) */
}
2015-11-05 04:36:52 -05:00
/* ---------------------------------------------------------------------------
* Lua reply to Redis reply conversion functions .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void luaReplyToRedisReply ( client * c , lua_State * lua ) {
int t = lua_type ( lua , - 1 ) ;
switch ( t ) {
case LUA_TSTRING :
addReplyBulkCBuffer ( c , ( char * ) lua_tostring ( lua , - 1 ) , lua_strlen ( lua , - 1 ) ) ;
break ;
case LUA_TBOOLEAN :
addReply ( c , lua_toboolean ( lua , - 1 ) ? shared . cone : shared . nullbulk ) ;
break ;
case LUA_TNUMBER :
addReplyLongLong ( c , ( long long ) lua_tonumber ( lua , - 1 ) ) ;
break ;
case LUA_TTABLE :
/* We need to check if it is an array, an error, or a status reply.
* Error are returned as a single element table with ' err ' field .
* Status replies are returned as single element table with ' ok '
* field . */
lua_pushstring ( lua , " err " ) ;
lua_gettable ( lua , - 2 ) ;
t = lua_type ( lua , - 1 ) ;
if ( t = = LUA_TSTRING ) {
sds err = sdsnew ( lua_tostring ( lua , - 1 ) ) ;
sdsmapchars ( err , " \r \n " , " " , 2 ) ;
addReplySds ( c , sdscatprintf ( sdsempty ( ) , " -%s \r \n " , err ) ) ;
sdsfree ( err ) ;
lua_pop ( lua , 2 ) ;
return ;
}
lua_pop ( lua , 1 ) ;
lua_pushstring ( lua , " ok " ) ;
lua_gettable ( lua , - 2 ) ;
t = lua_type ( lua , - 1 ) ;
if ( t = = LUA_TSTRING ) {
sds ok = sdsnew ( lua_tostring ( lua , - 1 ) ) ;
sdsmapchars ( ok , " \r \n " , " " , 2 ) ;
addReplySds ( c , sdscatprintf ( sdsempty ( ) , " +%s \r \n " , ok ) ) ;
sdsfree ( ok ) ;
lua_pop ( lua , 1 ) ;
} else {
void * replylen = addDeferredMultiBulkLength ( 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 + + ;
}
setDeferredMultiBulkLength ( c , replylen , mbulklen ) ;
}
break ;
default :
addReply ( c , shared . nullbulk ) ;
}
lua_pop ( lua , 1 ) ;
}
/* ---------------------------------------------------------------------------
* Lua redis . * functions implementations .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2014-05-06 09:39:14 -04:00
# define LUA_CMD_OBJCACHE_SIZE 32
# define LUA_CMD_OBJCACHE_MAX_LEN 64
2011-10-20 10:02:23 -04:00
int luaRedisGenericCommand ( lua_State * lua , int raise_error ) {
2011-04-30 16:29:21 -04:00
int j , argc = lua_gettop ( lua ) ;
struct redisCommand * cmd ;
2015-07-26 09:20:46 -04:00
client * c = server . lua_client ;
2011-04-30 16:29:21 -04:00
sds reply ;
2014-05-06 05:13:00 -04:00
/* Cached across calls. */
static robj * * argv = NULL ;
static int argv_size = 0 ;
2014-05-06 09:39:14 -04:00
static robj * cached_objects [ LUA_CMD_OBJCACHE_SIZE ] ;
2014-08-13 05:44:38 -04:00
static size_t cached_objects_len [ LUA_CMD_OBJCACHE_SIZE ] ;
2015-01-20 12:01:28 -05:00
static int inuse = 0 ; /* Recursive calls detection. */
/* By using Lua debug hooks it is possible to trigger a recursive call
* to luaRedisGenericCommand ( ) , which normally should never happen .
* To make this function reentrant is futile and makes it slower , but
* we should at least detect such a misuse , and abort . */
if ( inuse ) {
2015-01-20 17:20:12 -05:00
char * recursion_warning =
" luaRedisGenericCommand() recursive call detected. "
" Are you doing funny stuff with Lua debug hooks? " ;
2015-07-27 03:41:48 -04:00
serverLog ( LL_WARNING , " %s " , recursion_warning ) ;
2015-01-20 17:20:12 -05:00
luaPushError ( lua , recursion_warning ) ;
2015-01-20 17:13:47 -05:00
return 1 ;
2015-01-20 12:01:28 -05:00
}
inuse + + ;
2014-05-06 05:13:00 -04:00
2012-08-31 04:22:21 -04:00
/* Require at least one argument */
if ( argc = = 0 ) {
luaPushError ( lua ,
" Please specify at least one argument for redis.call() " ) ;
2015-01-20 12:01:28 -05:00
inuse - - ;
2015-11-09 05:43:18 -05:00
return raise_error ? luaRaiseError ( lua ) : 1 ;
2012-08-31 04:22:21 -04:00
}
2011-05-01 08:47:52 -04:00
/* Build the arguments vector */
2014-09-10 09:41:05 -04:00
if ( argv_size < argc ) {
2014-05-06 05:13:00 -04:00
argv = zrealloc ( argv , sizeof ( robj * ) * argc ) ;
argv_size = argc ;
}
2011-05-01 17:43:10 -04:00
for ( j = 0 ; j < argc ; j + + ) {
2014-05-06 09:04:02 -04:00
char * obj_s ;
size_t obj_len ;
2014-06-04 12:57:12 -04:00
char dbuf [ 64 ] ;
2014-05-06 09:04:02 -04:00
2014-06-11 04:10:58 -04:00
if ( lua_type ( lua , j + 1 ) = = LUA_TNUMBER ) {
2014-06-04 12:33:24 -04:00
/* We can't use lua_tolstring() for number -> string conversion
* since Lua uses a format specifier that loses precision . */
lua_Number num = lua_tonumber ( lua , j + 1 ) ;
obj_len = snprintf ( dbuf , sizeof ( dbuf ) , " %.17g " , ( double ) num ) ;
obj_s = dbuf ;
} else {
obj_s = ( char * ) lua_tolstring ( lua , j + 1 , & obj_len ) ;
if ( obj_s = = NULL ) break ; /* Not a string. */
}
2014-05-06 09:39:14 -04:00
/* Try to use a cached object. */
2014-06-04 12:33:24 -04:00
if ( j < LUA_CMD_OBJCACHE_SIZE & & cached_objects [ j ] & &
cached_objects_len [ j ] > = obj_len )
2014-05-19 16:18:13 -04:00
{
2015-04-09 03:37:01 -04:00
sds s = cached_objects [ j ] - > ptr ;
2014-05-06 09:39:14 -04:00
argv [ j ] = cached_objects [ j ] ;
cached_objects [ j ] = NULL ;
memcpy ( s , obj_s , obj_len + 1 ) ;
2015-04-09 03:37:01 -04:00
sdssetlen ( s , obj_len ) ;
2014-05-06 09:39:14 -04:00
} else {
argv [ j ] = createStringObject ( obj_s , obj_len ) ;
}
2011-05-01 17:43:10 -04:00
}
2014-05-20 10:11:22 -04:00
2011-05-01 17:43:10 -04:00
/* Check if one of the arguments passed by the Lua script
* is not a string or an integer ( lua_isstring ( ) return true for
* integers as well ) . */
if ( j ! = argc ) {
j - - ;
while ( j > = 0 ) {
decrRefCount ( argv [ j ] ) ;
j - - ;
}
luaPushError ( lua ,
" Lua redis() command arguments must be strings or integers " ) ;
2015-01-20 12:01:28 -05:00
inuse - - ;
2015-11-09 05:43:18 -05:00
return raise_error ? luaRaiseError ( lua ) : 1 ;
2011-05-01 17:43:10 -04:00
}
2011-04-30 16:29:21 -04:00
2011-09-27 07:57:10 -04:00
/* Setup our fake client for command execution */
c - > argv = argv ;
c - > argc = argc ;
2015-11-09 11:01:41 -05:00
/* Log the command if debugging is active. */
if ( ldb . active & & ldb . step ) {
sds cmdlog = sdsnew ( " <redis> " ) ;
for ( j = 0 ; j < c - > argc ; j + + ) {
if ( j = = 10 ) {
cmdlog = sdscatprintf ( cmdlog , " ... (%d more) " ,
c - > argc - j - 1 ) ;
} else {
cmdlog = sdscatlen ( cmdlog , " " , 1 ) ;
cmdlog = sdscatsds ( cmdlog , c - > argv [ j ] - > ptr ) ;
}
}
ldbLog ( cmdlog ) ;
}
2011-04-30 16:29:21 -04:00
/* Command lookup */
cmd = lookupCommand ( argv [ 0 ] - > ptr ) ;
2011-05-01 09:26:47 -04:00
if ( ! cmd | | ( ( cmd - > arity > 0 & & cmd - > arity ! = argc ) | |
( argc < - cmd - > arity ) ) )
{
if ( cmd )
2011-05-01 17:43:10 -04:00
luaPushError ( lua ,
2011-05-01 09:26:47 -04:00
" Wrong number of args calling Redis command From Lua script " ) ;
else
2011-05-01 17:43:10 -04:00
luaPushError ( lua , " Unknown Redis command called from Lua script " ) ;
2011-09-27 07:57:10 -04:00
goto cleanup ;
2011-04-30 16:29:21 -04:00
}
2015-01-09 04:39:05 -05:00
c - > cmd = cmd ;
2011-05-01 08:47:52 -04:00
2012-03-20 12:32:48 -04:00
/* There are commands that are not allowed inside scripts. */
2015-07-27 03:41:48 -04:00
if ( cmd - > flags & CMD_NOSCRIPT ) {
2011-09-27 07:57:10 -04:00
luaPushError ( lua , " This Redis command is not allowed from scripts " ) ;
goto cleanup ;
}
2012-03-20 12:32:48 -04:00
/* Write commands are forbidden against read-only slaves, or if a
* command marked as non - deterministic was already called in the context
* of this script . */
2015-07-27 03:41:48 -04:00
if ( cmd - > flags & CMD_WRITE ) {
2015-10-29 07:39:07 -04:00
if ( server . lua_random_dirty & & ! server . lua_replicate_commands ) {
2012-03-20 12:32:48 -04:00
luaPushError ( lua ,
2015-10-29 07:39:07 -04:00
" Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode. " ) ;
2012-03-20 12:32:48 -04:00
goto cleanup ;
} else if ( server . masterhost & & server . repl_slave_ro & &
2013-06-19 12:25:03 -04:00
! server . loading & &
2015-07-27 03:41:48 -04:00
! ( server . lua_caller - > flags & CLIENT_MASTER ) )
2012-03-20 12:32:48 -04:00
{
luaPushError ( lua , shared . roslaveerr - > ptr ) ;
goto cleanup ;
} else if ( server . stop_writes_on_bgsave_err & &
server . saveparamslen > 0 & &
2015-07-26 17:17:55 -04:00
server . lastbgsave_status = = C_ERR )
2012-03-20 12:32:48 -04:00
{
luaPushError ( lua , shared . bgsaveerr - > ptr ) ;
goto cleanup ;
}
}
/* If we reached the memory limit configured via maxmemory, commands that
* could enlarge the memory usage are not allowed , but only if this is the
* first write in the context of this script , otherwise we can ' t stop
* in the middle . */
if ( server . maxmemory & & server . lua_write_dirty = = 0 & &
2015-07-27 03:41:48 -04:00
( cmd - > flags & CMD_DENYOOM ) )
2012-03-20 12:32:48 -04:00
{
2015-07-26 17:17:55 -04:00
if ( freeMemoryIfNeeded ( ) = = C_ERR ) {
2012-03-20 12:32:48 -04:00
luaPushError ( lua , shared . oomerr - > ptr ) ;
goto cleanup ;
}
2011-09-27 09:30:31 -04:00
}
2015-07-27 03:41:48 -04:00
if ( cmd - > flags & CMD_RANDOM ) server . lua_random_dirty = 1 ;
if ( cmd - > flags & CMD_WRITE ) server . lua_write_dirty = 1 ;
2011-09-27 09:30:31 -04:00
2015-01-09 04:39:05 -05:00
/* If this is a Redis Cluster node, we need to make sure Lua is not
2015-03-22 17:23:41 -04:00
* trying to access non - local keys , with the exception of commands
* received from our master . */
2015-07-27 03:41:48 -04:00
if ( server . cluster_enabled & & ! ( server . lua_caller - > flags & CLIENT_MASTER ) ) {
2015-01-09 04:39:05 -05:00
/* Duplicate relevant flags in the lua client. */
2015-07-27 03:41:48 -04:00
c - > flags & = ~ ( CLIENT_READONLY | CLIENT_ASKING ) ;
c - > flags | = server . lua_caller - > flags & ( CLIENT_READONLY | CLIENT_ASKING ) ;
2015-01-09 04:39:05 -05:00
if ( getNodeByQuery ( c , c - > cmd , c - > argv , c - > argc , NULL , NULL ) ! =
server . cluster - > myself )
{
luaPushError ( lua ,
" Lua script attempted to access a non local key in a "
" cluster node " ) ;
goto cleanup ;
}
}
2015-10-29 07:39:07 -04:00
/* If we are using single commands replication, we need to wrap what
* we propagate into a MULTI / EXEC block , so that it will be atomic like
* a Lua script in the context of AOF and slaves . */
if ( server . lua_replicate_commands & &
! server . lua_multi_emitted & &
2015-10-29 08:50:04 -04:00
server . lua_write_dirty & &
server . lua_repl ! = PROPAGATE_NONE )
2015-10-29 07:39:07 -04:00
{
execCommandPropagateMulti ( server . lua_caller ) ;
server . lua_multi_emitted = 1 ;
}
2011-09-27 07:57:10 -04:00
/* Run the command */
2015-10-29 07:39:07 -04:00
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS ;
2015-10-29 08:50:04 -04:00
if ( server . lua_replicate_commands ) {
2015-10-29 10:57:41 -04:00
/* Set flags according to redis.set_repl() settings. */
if ( server . lua_repl & PROPAGATE_AOF )
call_flags | = CMD_CALL_PROPAGATE_AOF ;
if ( server . lua_repl & PROPAGATE_REPL )
call_flags | = CMD_CALL_PROPAGATE_REPL ;
2015-10-29 08:50:04 -04:00
}
2015-10-29 07:39:07 -04:00
call ( c , call_flags ) ;
2011-04-30 16:29:21 -04:00
/* Convert the result of the Redis command into a suitable Lua type.
* The first thing we need is to create a single string from the client
* output buffers . */
2015-07-27 03:41:48 -04:00
if ( listLength ( c - > reply ) = = 0 & & c - > bufpos < PROTO_REPLY_CHUNK_BYTES ) {
2014-05-05 16:14:23 -04:00
/* This is a fast path for the common case of a reply inside the
* client static buffer . Don ' t create an SDS string but just use
* the client buffer directly . */
c - > buf [ c - > bufpos ] = ' \0 ' ;
reply = c - > buf ;
2014-05-06 04:34:22 -04:00
c - > bufpos = 0 ;
2014-05-05 16:14:23 -04:00
} else {
reply = sdsnewlen ( c - > buf , c - > bufpos ) ;
c - > bufpos = 0 ;
while ( listLength ( c - > reply ) ) {
2015-07-31 08:59:54 -04:00
sds o = listNodeValue ( listFirst ( c - > reply ) ) ;
2011-04-30 16:29:21 -04:00
2015-07-31 08:59:54 -04:00
reply = sdscatsds ( reply , o ) ;
2014-05-05 16:14:23 -04:00
listDelNode ( c - > reply , listFirst ( c - > reply ) ) ;
}
2011-04-30 16:29:21 -04:00
}
2011-10-20 10:02:23 -04:00
if ( raise_error & & reply [ 0 ] ! = ' - ' ) raise_error = 0 ;
2011-05-01 08:47:52 -04:00
redisProtocolToLuaType ( lua , reply ) ;
2015-11-09 11:01:41 -05:00
/* If the debugger is active, log the reply from Redis. */
2015-11-11 04:15:26 -05:00
if ( ldb . active & & ldb . step )
ldbLogRedisReply ( reply , LDB_REPLY_MAX_LOG_LEN ) ;
2015-11-09 11:01:41 -05:00
2012-01-31 10:09:21 -05:00
/* Sort the output array if needed, assuming it is a non-null multi bulk
* reply as expected . */
2015-07-27 03:41:48 -04:00
if ( ( cmd - > flags & CMD_SORT_FOR_SCRIPT ) & &
2015-10-29 07:39:07 -04:00
( server . lua_replicate_commands = = 0 ) & &
2012-01-31 10:09:21 -05:00
( reply [ 0 ] = = ' * ' & & reply [ 1 ] ! = ' - ' ) ) {
2012-02-01 09:22:28 -05:00
luaSortArray ( lua ) ;
2012-01-31 10:09:21 -05:00
}
2014-05-05 16:14:23 -04:00
if ( reply ! = c - > buf ) sdsfree ( reply ) ;
Scripting: Reset Lua fake client reply_bytes after command execution.
Lua scripting uses a fake client in order to run commands in the context
of a client, accumulate the reply, and convert it into a Lua object
to return to the caller. This client is reused again and again, and is
referenced by the server.lua_client globally accessible pointer.
However after every call to redis.call() or redis.pcall(), that is
handled by the luaRedisGenericCommand() function, the reply_bytes field
of the client was not set back to zero. This filed is used to estimate
the amount of memory currently used in the reply. Because of the lack of
reset, script after script executed, this value used to get bigger and
bigger, and in the end on 32 bit systems it triggered the following
assert:
redisAssert(c->reply_bytes < ULONG_MAX-(1024*64));
On 64 bit systems this does not happen because it takes too much time to
reach values near to 2^64 for users to see the practical effect of the
bug.
Now in the cleanup stage of luaRedisGenericCommand() we reset the
reply_bytes counter to zero, avoiding the issue. It is not practical to
add a test for this bug, but the fix was manually tested using a
debugger.
This commit fixes issue #656.
2012-08-31 05:08:53 -04:00
c - > reply_bytes = 0 ;
2011-04-30 16:29:21 -04:00
2011-09-27 07:57:10 -04:00
cleanup :
2011-04-30 16:29:21 -04:00
/* Clean up. Command code may have changed argv/argc so we use the
* argv / argc of the client instead of the local variables . */
2014-05-06 09:39:14 -04:00
for ( j = 0 ; j < c - > argc ; j + + ) {
robj * o = c - > argv [ j ] ;
/* Try to cache the object in the cached_objects array.
* The object must be small , SDS - encoded , and with refcount = 1
* ( we must be the only owner ) for us to cache it . */
if ( j < LUA_CMD_OBJCACHE_SIZE & &
o - > refcount = = 1 & &
2015-07-26 09:28:00 -04:00
( o - > encoding = = OBJ_ENCODING_RAW | |
o - > encoding = = OBJ_ENCODING_EMBSTR ) & &
2014-05-06 09:39:14 -04:00
sdslen ( o - > ptr ) < = LUA_CMD_OBJCACHE_MAX_LEN )
{
2015-04-09 03:37:01 -04:00
sds s = o - > ptr ;
2014-05-06 09:39:14 -04:00
if ( cached_objects [ j ] ) decrRefCount ( cached_objects [ j ] ) ;
cached_objects [ j ] = o ;
2015-04-09 03:37:01 -04:00
cached_objects_len [ j ] = sdsalloc ( s ) ;
2014-05-06 09:39:14 -04:00
} else {
decrRefCount ( o ) ;
}
}
2014-05-06 05:13:00 -04:00
if ( c - > argv ! = argv ) {
zfree ( c - > argv ) ;
argv = NULL ;
2014-09-10 09:41:05 -04:00
argv_size = 0 ;
2014-05-06 05:13:00 -04:00
}
2011-04-30 16:29:21 -04:00
2011-10-20 10:02:23 -04:00
if ( raise_error ) {
/* If we are here we should have an error in the stack, in the
* form of a table with an " err " field . Extract the string to
* return the plain error . */
2015-01-20 12:01:28 -05:00
inuse - - ;
2015-11-09 05:43:18 -05:00
return luaRaiseError ( lua ) ;
2011-10-20 10:02:23 -04:00
}
2015-01-20 12:01:28 -05:00
inuse - - ;
2011-04-30 16:29:21 -04:00
return 1 ;
}
2015-10-29 07:39:07 -04:00
/* redis.call() */
2011-10-20 10:02:23 -04:00
int luaRedisCallCommand ( lua_State * lua ) {
return luaRedisGenericCommand ( lua , 1 ) ;
}
2015-10-29 07:39:07 -04:00
/* redis.pcall() */
2011-10-20 10:02:23 -04:00
int luaRedisPCallCommand ( lua_State * lua ) {
return luaRedisGenericCommand ( lua , 0 ) ;
}
2012-03-28 14:10:24 -04:00
/* This adds redis.sha1hex(string) to Lua scripts using the same hashing
* function used for sha1ing lua scripts . */
int luaRedisSha1hexCommand ( lua_State * lua ) {
int argc = lua_gettop ( lua ) ;
char digest [ 41 ] ;
size_t len ;
char * s ;
if ( argc ! = 1 ) {
2015-10-30 04:53:45 -04:00
lua_pushstring ( lua , " wrong number of arguments " ) ;
return lua_error ( lua ) ;
2012-03-28 14:10:24 -04:00
}
s = ( char * ) lua_tolstring ( lua , 1 , & len ) ;
sha1hex ( digest , s , len ) ;
lua_pushstring ( lua , digest ) ;
return 1 ;
}
2012-09-28 10:54:57 -04:00
/* 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 ;
}
2015-10-29 07:39:07 -04:00
/* redis.error_reply() */
2012-09-28 10:54:57 -04:00
int luaRedisErrorReplyCommand ( lua_State * lua ) {
return luaRedisReturnSingleFieldTable ( lua , " err " ) ;
}
2015-10-29 07:39:07 -04:00
/* redis.status_reply() */
2012-09-28 10:54:57 -04:00
int luaRedisStatusReplyCommand ( lua_State * lua ) {
return luaRedisReturnSingleFieldTable ( lua , " ok " ) ;
}
2015-10-29 07:39:07 -04:00
/* 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 ) {
if ( server . lua_write_dirty ) {
lua_pushboolean ( lua , 0 ) ;
} else {
server . lua_replicate_commands = 1 ;
/* 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 ;
}
2015-11-11 04:28:35 -05:00
/* redis.breakpoint()
*
* Allows to stop execution during a debuggign 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 ;
}
2015-11-12 02:33:52 -05:00
/* 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 ( ) , " <debug> line %d: " , ldb . currentline ) ;
while ( argc - - ) {
log = ldbCatStackValue ( log , lua , - 1 - argc ) ;
if ( argc ! = 0 ) log = sdscatlen ( log , " , " , 2 ) ;
}
ldbLog ( log ) ;
return 0 ;
}
2015-10-29 08:50:04 -04:00
/* redis.set_repl()
*
* Set the propagation of write commands executed in the context of the
* script to on / off for AOF and slaves . */
int luaRedisSetReplCommand ( lua_State * lua ) {
int argc = lua_gettop ( lua ) ;
int flags ;
if ( server . lua_replicate_commands = = 0 ) {
2015-10-30 04:53:45 -04:00
lua_pushstring ( lua , " You can set the replication behavior only after turning on single commands replication with redis.replicate_commands(). " ) ;
return lua_error ( lua ) ;
2015-10-29 08:50:04 -04:00
} else if ( argc ! = 1 ) {
2015-10-30 04:53:45 -04:00
lua_pushstring ( lua , " redis.set_repl() requires two arguments. " ) ;
return lua_error ( lua ) ;
2015-10-29 08:50:04 -04:00
}
flags = lua_tonumber ( lua , - 1 ) ;
if ( ( flags & ~ ( PROPAGATE_AOF | PROPAGATE_REPL ) ) ! = 0 ) {
2015-10-30 04:53:45 -04:00
lua_pushstring ( lua , " Invalid replication flags. Use REPL_AOF, REPL_SLAVE, REPL_ALL or REPL_NONE. " ) ;
return lua_error ( lua ) ;
2015-10-29 08:50:04 -04:00
}
server . lua_repl = flags ;
return 0 ;
}
2015-10-29 07:39:07 -04:00
/* redis.log() */
2011-05-16 12:32:03 -04:00
int luaLogCommand ( lua_State * lua ) {
int j , argc = lua_gettop ( lua ) ;
int level ;
sds log ;
if ( argc < 2 ) {
2015-10-30 04:53:45 -04:00
lua_pushstring ( lua , " redis.log() requires two arguments or more. " ) ;
return lua_error ( lua ) ;
2011-05-16 12:32:03 -04:00
} else if ( ! lua_isnumber ( lua , - argc ) ) {
2015-10-30 04:53:45 -04:00
lua_pushstring ( lua , " First argument must be a number (log level). " ) ;
return lua_error ( lua ) ;
2011-05-16 12:32:03 -04:00
}
level = lua_tonumber ( lua , - argc ) ;
2015-07-27 03:41:48 -04:00
if ( level < LL_DEBUG | | level > LL_WARNING ) {
2015-10-30 04:53:45 -04:00
lua_pushstring ( lua , " Invalid debug level. " ) ;
return lua_error ( lua ) ;
2011-05-16 12:32:03 -04:00
}
/* Glue together all the arguments */
log = sdsempty ( ) ;
for ( j = 1 ; j < argc ; j + + ) {
size_t len ;
char * s ;
s = ( char * ) lua_tolstring ( lua , ( - argc ) + j , & len ) ;
if ( s ) {
if ( j ! = 1 ) log = sdscatlen ( log , " " , 1 ) ;
log = sdscatlen ( log , s , len ) ;
}
}
2015-07-26 09:17:43 -04:00
serverLogRaw ( level , log ) ;
2011-05-16 12:32:03 -04:00
sdsfree ( log ) ;
return 0 ;
}
2015-11-05 04:36:52 -05:00
/* ---------------------------------------------------------------------------
* Lua engine initialization and reset .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2011-05-06 11:21:27 -04:00
2011-09-27 12:46:23 -04:00
void luaLoadLib ( lua_State * lua , const char * libname , lua_CFunction luafunc ) {
lua_pushcfunction ( lua , luafunc ) ;
lua_pushstring ( lua , libname ) ;
lua_call ( lua , 1 , 0 ) ;
}
2011-10-19 10:42:10 -04:00
LUALIB_API int ( luaopen_cjson ) ( lua_State * L ) ;
2012-02-13 16:05:21 -05:00
LUALIB_API int ( luaopen_struct ) ( lua_State * L ) ;
2012-02-24 09:45:16 -05:00
LUALIB_API int ( luaopen_cmsgpack ) ( lua_State * L ) ;
Lua: Add bitop
A few people have written custom C commands because bit
manipulation isn't exposed through Lua. Let's give
them Mike Pall's bitop.
This adds bitop 1.0.2 (2012-05-08) from http://bitop.luajit.org/
bitop is imported as "bit" into the global namespace.
New Lua commands: bit.tobit, bit.tohex, bit.bnot, bit.band, bit.bor, bit.bxor,
bit.lshift, bit.rshift, bit.arshift, bit.rol, bit.ror, bit.bswap
Verification of working (the asserts would abort on error, so (nil) is correct):
127.0.0.1:6379> eval "assert(bit.tobit(1) == 1); assert(bit.band(1) == 1); assert(bit.bxor(1,2) == 3); assert(bit.bor(1,2,4,8,16,32,64,128) == 255)" 0
(nil)
127.0.0.1:6379> eval 'assert(0x7fffffff == 2147483647, "broken hex literals"); assert(0xffffffff == -1 or 0xffffffff == 2^32-1, "broken hex literals"); assert(tostring(-1) == "-1", "broken tostring()"); assert(tostring(0xffffffff) == "-1" or tostring(0xffffffff) == "4294967295", "broken tostring()")' 0
(nil)
Tests also integrated into the scripting tests and can be run with:
./runtest --single unit/scripting
Tests are excerpted from `bittest.lua` included in the bitop distribution.
2014-04-04 11:34:36 -04:00
LUALIB_API int ( luaopen_bit ) ( lua_State * L ) ;
2011-10-19 10:42:10 -04:00
2011-09-27 12:46:23 -04:00
void luaLoadLibraries ( lua_State * lua ) {
luaLoadLib ( lua , " " , luaopen_base ) ;
luaLoadLib ( lua , LUA_TABLIBNAME , luaopen_table ) ;
luaLoadLib ( lua , LUA_STRLIBNAME , luaopen_string ) ;
luaLoadLib ( lua , LUA_MATHLIBNAME , luaopen_math ) ;
2014-05-20 10:11:22 -04:00
luaLoadLib ( lua , LUA_DBLIBNAME , luaopen_debug ) ;
2012-02-13 16:05:21 -05:00
luaLoadLib ( lua , " cjson " , luaopen_cjson ) ;
luaLoadLib ( lua , " struct " , luaopen_struct ) ;
2012-02-24 09:45:16 -05:00
luaLoadLib ( lua , " cmsgpack " , luaopen_cmsgpack ) ;
Lua: Add bitop
A few people have written custom C commands because bit
manipulation isn't exposed through Lua. Let's give
them Mike Pall's bitop.
This adds bitop 1.0.2 (2012-05-08) from http://bitop.luajit.org/
bitop is imported as "bit" into the global namespace.
New Lua commands: bit.tobit, bit.tohex, bit.bnot, bit.band, bit.bor, bit.bxor,
bit.lshift, bit.rshift, bit.arshift, bit.rol, bit.ror, bit.bswap
Verification of working (the asserts would abort on error, so (nil) is correct):
127.0.0.1:6379> eval "assert(bit.tobit(1) == 1); assert(bit.band(1) == 1); assert(bit.bxor(1,2) == 3); assert(bit.bor(1,2,4,8,16,32,64,128) == 255)" 0
(nil)
127.0.0.1:6379> eval 'assert(0x7fffffff == 2147483647, "broken hex literals"); assert(0xffffffff == -1 or 0xffffffff == 2^32-1, "broken hex literals"); assert(tostring(-1) == "-1", "broken tostring()"); assert(tostring(0xffffffff) == "-1" or tostring(0xffffffff) == "4294967295", "broken tostring()")' 0
(nil)
Tests also integrated into the scripting tests and can be run with:
./runtest --single unit/scripting
Tests are excerpted from `bittest.lua` included in the bitop distribution.
2014-04-04 11:34:36 -04:00
luaLoadLib ( lua , " bit " , luaopen_bit ) ;
2011-09-27 12:46:23 -04:00
#if 0 /* Stuff that we don't load currently, for sandboxing concerns. */
luaLoadLib ( lua , LUA_LOADLIBNAME , luaopen_package ) ;
luaLoadLib ( lua , LUA_OSLIBNAME , luaopen_os ) ;
# endif
}
2012-04-23 04:43:24 -04:00
/* 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 " ) ;
}
2012-04-13 05:16:50 -04:00
/* 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
2012-04-13 07:26:59 -04:00
* sequence , because it may interact with creation of globals . */
2012-04-13 05:16:50 -04:00
void scriptingEnableGlobalsProtection ( lua_State * lua ) {
char * s [ 32 ] ;
2012-03-29 06:02:28 -04:00
sds code = sdsempty ( ) ;
2012-04-13 05:16:50 -04:00
int j = 0 ;
2012-03-29 06:02:28 -04:00
2012-04-13 05:16:50 -04:00
/* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html.
* Modified to be adapted to Redis . */
2015-05-11 18:24:37 -04:00
s [ j + + ] = " local dbg=debug \n " ;
2012-04-13 06:13:02 -04:00
s [ j + + ] = " local mt = {} \n " ;
2012-04-13 05:16:50 -04:00
s [ j + + ] = " setmetatable(_G, mt) \n " ;
s [ j + + ] = " mt.__newindex = function (t, n, v) \n " ;
2015-05-11 18:24:37 -04:00
s [ j + + ] = " if dbg.getinfo(2) then \n " ;
s [ j + + ] = " local w = dbg.getinfo(2, \" S \" ).what \n " ;
2012-04-13 05:16:50 -04:00
s [ j + + ] = " if w ~= \" main \" and w ~= \" C \" then \n " ;
2012-04-13 07:26:59 -04:00
s [ j + + ] = " error( \" Script attempted to create global variable ' \" ..tostring(n).. \" ' \" , 2) \n " ;
2012-04-13 05:16:50 -04:00
s [ j + + ] = " end \n " ;
s [ j + + ] = " end \n " ;
s [ j + + ] = " rawset(t, n, v) \n " ;
s [ j + + ] = " end \n " ;
s [ j + + ] = " mt.__index = function (t, n) \n " ;
2015-05-11 18:24:37 -04:00
s [ j + + ] = " if dbg.getinfo(2) and dbg.getinfo(2, \" S \" ).what ~= \" C \" then \n " ;
2012-04-13 08:54:49 -04:00
s [ j + + ] = " error( \" Script attempted to access unexisting global variable ' \" ..tostring(n).. \" ' \" , 2) \n " ;
2012-04-13 05:16:50 -04:00
s [ j + + ] = " end \n " ;
s [ j + + ] = " return rawget(t, n) \n " ;
s [ j + + ] = " end \n " ;
2015-05-11 18:24:37 -04:00
s [ j + + ] = " debug = nil \n " ;
2012-04-13 05:16:50 -04:00
s [ j + + ] = NULL ;
for ( j = 0 ; s [ j ] ! = NULL ; j + + ) code = sdscatlen ( code , s [ j ] , strlen ( s [ j ] ) ) ;
2012-04-13 09:12:16 -04:00
luaL_loadbuffer ( lua , code , sdslen ( code ) , " @enable_strict_lua " ) ;
2012-03-29 06:02:28 -04:00
lua_pcall ( lua , 0 , 0 , 0 ) ;
sdsfree ( code ) ;
}
2011-10-24 16:47:00 -04:00
/* Initialize the scripting environment.
2015-11-05 05:10:46 -05:00
*
* 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 ) {
2011-04-30 11:46:52 -04:00
lua_State * lua = lua_open ( ) ;
2012-04-23 04:43:24 -04:00
2015-11-05 05:10:46 -05:00
if ( setup ) {
server . lua_client = NULL ;
server . lua_caller = NULL ;
server . lua_timedout = 0 ;
server . lua_always_replicate_commands = 0 ; /* Only DEBUG can change it.*/
server . lua_time_limit = LUA_SCRIPT_TIME_LIMIT ;
2015-11-06 10:19:59 -05:00
ldbInit ( ) ;
2015-11-05 05:10:46 -05:00
}
2011-09-27 12:46:23 -04:00
luaLoadLibraries ( lua ) ;
2012-04-23 04:43:24 -04:00
luaRemoveUnsupportedFunctions ( lua ) ;
2011-04-30 16:29:21 -04:00
2011-07-13 09:38:03 -04:00
/* 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 . */
2012-11-22 09:50:00 -05:00
server . lua_scripts = dictCreate ( & shaScriptObjectDictType , NULL ) ;
2011-07-13 09:38:03 -04:00
2011-05-16 12:32:03 -04:00
/* Register the redis commands table and fields */
lua_newtable ( lua ) ;
/* redis.call */
lua_pushstring ( lua , " call " ) ;
2011-10-20 10:02:23 -04:00
lua_pushcfunction ( lua , luaRedisCallCommand ) ;
lua_settable ( lua , - 3 ) ;
/* redis.pcall */
lua_pushstring ( lua , " pcall " ) ;
lua_pushcfunction ( lua , luaRedisPCallCommand ) ;
2011-05-16 12:32:03 -04:00
lua_settable ( lua , - 3 ) ;
/* redis.log and log levels. */
lua_pushstring ( lua , " log " ) ;
lua_pushcfunction ( lua , luaLogCommand ) ;
lua_settable ( lua , - 3 ) ;
2015-07-27 03:41:48 -04:00
lua_pushstring ( lua , " LL_DEBUG " ) ;
lua_pushnumber ( lua , LL_DEBUG ) ;
2011-05-16 12:32:03 -04:00
lua_settable ( lua , - 3 ) ;
2015-07-27 03:41:48 -04:00
lua_pushstring ( lua , " LL_VERBOSE " ) ;
lua_pushnumber ( lua , LL_VERBOSE ) ;
2011-05-16 12:32:03 -04:00
lua_settable ( lua , - 3 ) ;
2015-07-27 03:41:48 -04:00
lua_pushstring ( lua , " LL_NOTICE " ) ;
lua_pushnumber ( lua , LL_NOTICE ) ;
2011-05-16 12:32:03 -04:00
lua_settable ( lua , - 3 ) ;
2015-07-27 03:41:48 -04:00
lua_pushstring ( lua , " LL_WARNING " ) ;
lua_pushnumber ( lua , LL_WARNING ) ;
2011-05-16 12:32:03 -04:00
lua_settable ( lua , - 3 ) ;
2012-03-28 14:10:24 -04:00
/* redis.sha1hex */
lua_pushstring ( lua , " sha1hex " ) ;
lua_pushcfunction ( lua , luaRedisSha1hexCommand ) ;
lua_settable ( lua , - 3 ) ;
2012-09-28 10:54:57 -04:00
/* 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 ) ;
2015-10-29 07:39:07 -04:00
/* redis.replicate_commands */
lua_pushstring ( lua , " replicate_commands " ) ;
lua_pushcfunction ( lua , luaRedisReplicateCommandsCommand ) ;
lua_settable ( lua , - 3 ) ;
2015-10-29 08:50:04 -04:00
/* redis.set_repl and associated flags. */
lua_pushstring ( lua , " set_repl " ) ;
lua_pushcfunction ( lua , luaRedisSetReplCommand ) ;
lua_settable ( lua , - 3 ) ;
lua_pushstring ( lua , " REPL_NONE " ) ;
lua_pushnumber ( lua , PROPAGATE_NONE ) ;
lua_settable ( lua , - 3 ) ;
lua_pushstring ( lua , " REPL_AOF " ) ;
lua_pushnumber ( lua , PROPAGATE_AOF ) ;
lua_settable ( lua , - 3 ) ;
lua_pushstring ( lua , " REPL_SLAVE " ) ;
lua_pushnumber ( lua , PROPAGATE_REPL ) ;
lua_settable ( lua , - 3 ) ;
lua_pushstring ( lua , " REPL_ALL " ) ;
lua_pushnumber ( lua , PROPAGATE_AOF | PROPAGATE_REPL ) ;
lua_settable ( lua , - 3 ) ;
2015-11-11 04:28:35 -05:00
/* redis.breakpoint */
lua_pushstring ( lua , " breakpoint " ) ;
lua_pushcfunction ( lua , luaRedisBreakpointCommand ) ;
lua_settable ( lua , - 3 ) ;
2015-11-12 02:33:52 -05:00
/* redis.debug */
lua_pushstring ( lua , " debug " ) ;
lua_pushcfunction ( lua , luaRedisDebugCommand ) ;
lua_settable ( lua , - 3 ) ;
2011-05-16 12:32:03 -04:00
/* Finally set the table as 'redis' global var. */
2011-05-01 06:49:02 -04:00
lua_setglobal ( lua , " redis " ) ;
2011-04-30 16:29:21 -04:00
2011-09-23 09:40:58 -04:00
/* Replace math.random and math.randomseed with our implementations. */
lua_getglobal ( lua , " math " ) ;
lua_pushstring ( lua , " random " ) ;
lua_pushcfunction ( lua , redis_math_random ) ;
lua_settable ( lua , - 3 ) ;
lua_pushstring ( lua , " randomseed " ) ;
lua_pushcfunction ( lua , redis_math_randomseed ) ;
lua_settable ( lua , - 3 ) ;
lua_setglobal ( lua , " math " ) ;
2013-01-16 12:00:20 -05:00
/* Add a helper function that we use to sort the multi bulk output of non
2012-02-01 09:22:28 -05:00
* 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 a<b \n "
" end \n " ;
2012-04-13 09:12:16 -04:00
luaL_loadbuffer ( lua , compare_func , strlen ( compare_func ) , " @cmp_func_def " ) ;
2012-02-01 09:22:28 -05:00
lua_pcall ( lua , 0 , 0 , 0 ) ;
}
2013-06-18 11:33:35 -04:00
/* Add a helper function we use for pcall error reporting.
* Note that when the error is in the C function we want to report the
* information about the caller , that ' s what makes sense from the point
* of view of the user debugging a script . */
{
2015-05-11 18:24:37 -04:00
char * errh_func = " local dbg = debug \n "
" function __redis__err__handler(err) \n "
" local i = dbg.getinfo(2,'nSl') \n "
2013-06-18 11:33:35 -04:00
" if i and i.what == 'C' then \n "
2015-05-11 18:24:37 -04:00
" i = dbg.getinfo(3,'nSl') \n "
2013-06-18 11:33:35 -04:00
" end \n "
" if i then \n "
2013-06-18 13:30:56 -04:00
" return i.source .. ':' .. i.currentline .. ': ' .. err \n "
2013-06-18 11:33:35 -04:00
" else \n "
" return err \n "
" end \n "
" end \n " ;
luaL_loadbuffer ( lua , errh_func , strlen ( errh_func ) , " @err_handler_def " ) ;
lua_pcall ( lua , 0 , 0 , 0 ) ;
}
2011-04-30 16:29:21 -04:00
/* Create the (non connected) client that we use to execute Redis commands
2011-10-24 16:47:00 -04:00
* inside the Lua interpreter .
* 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 ) ;
2015-07-27 03:41:48 -04:00
server . lua_client - > flags | = CLIENT_LUA ;
2011-10-24 16:47:00 -04:00
}
2011-04-30 16:29:21 -04:00
2014-08-23 12:17:54 -04:00
/* Lua beginners often don't use "local", this is likely to introduce
2012-03-29 06:02:28 -04:00
* subtle bugs in their code . To prevent problems we protect accesses
* to global variables . */
2012-04-13 07:26:59 -04:00
scriptingEnableGlobalsProtection ( lua ) ;
2012-03-29 06:02:28 -04:00
2011-04-30 11:46:52 -04:00
server . lua = lua ;
}
2011-10-24 16:47:00 -04:00
/* Release resources related to Lua scripting.
* This function is used in order to reset the scripting environment . */
void scriptingRelease ( void ) {
dictRelease ( server . lua_scripts ) ;
lua_close ( server . lua ) ;
}
void scriptingReset ( void ) {
scriptingRelease ( ) ;
2015-11-05 05:10:46 -05:00
scriptingInit ( 0 ) ;
2011-10-24 16:47:00 -04:00
}
2011-05-01 07:07:44 -04:00
/* Set an array of Redis String Objects as a Lua array (table) stored into a
* global variable . */
void luaSetGlobalArray ( lua_State * lua , char * var , robj * * elev , int elec ) {
int j ;
lua_newtable ( lua ) ;
for ( j = 0 ; j < elec ; j + + ) {
lua_pushlstring ( lua , ( char * ) elev [ j ] - > ptr , sdslen ( elev [ j ] - > ptr ) ) ;
lua_rawseti ( lua , - 2 , j + 1 ) ;
}
lua_setglobal ( lua , var ) ;
}
2015-11-05 04:36:52 -05:00
/* ---------------------------------------------------------------------------
* Redis provided math . random
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* We replace math.random() with our implementation that is not affected
* by specific libc random ( ) implementations and will output the same sequence
* ( for the same seed ) in every arch . */
/* The following implementation is the one shipped with Lua itself but with
* rand ( ) replaced by redisLrand48 ( ) . */
int redis_math_random ( lua_State * L ) {
/* the `%' avoids the (rare) case of r==1, and is needed also because on
some systems ( SunOS ! ) ` rand ( ) ' may return a value larger than RAND_MAX */
lua_Number r = ( lua_Number ) ( redisLrand48 ( ) % REDIS_LRAND48_MAX ) /
( lua_Number ) REDIS_LRAND48_MAX ;
switch ( lua_gettop ( L ) ) { /* check number of arguments */
case 0 : { /* no arguments */
lua_pushnumber ( L , r ) ; /* Number between 0 and 1 */
break ;
}
case 1 : { /* only upper limit */
int u = luaL_checkint ( L , 1 ) ;
luaL_argcheck ( L , 1 < = u , 1 , " interval is empty " ) ;
lua_pushnumber ( L , floor ( r * u ) + 1 ) ; /* int between 1 and `u' */
break ;
}
case 2 : { /* lower and upper limits */
int l = luaL_checkint ( L , 1 ) ;
int u = luaL_checkint ( L , 2 ) ;
luaL_argcheck ( L , l < = u , 2 , " interval is empty " ) ;
lua_pushnumber ( L , floor ( r * ( u - l + 1 ) ) + l ) ; /* int between `l' and `u' */
break ;
}
default : return luaL_error ( L , " wrong number of arguments " ) ;
}
return 1 ;
}
int redis_math_randomseed ( lua_State * L ) {
redisSrand48 ( luaL_checkint ( L , 1 ) ) ;
return 0 ;
}
/* ---------------------------------------------------------------------------
* EVAL and SCRIPT commands implementation
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2011-10-25 04:25:59 -04:00
/* Define a lua function with the specified function name and body.
2015-07-14 10:32:53 -04:00
* The function name musts be a 42 characters long string , since all the
2011-10-25 04:25:59 -04:00
* functions we defined in the Lua context are in the form :
*
* f_ < hex sha1 sum >
*
2015-07-26 17:17:55 -04:00
* On success C_OK is returned , and nothing is left on the Lua stack .
* On error C_ERR is returned and an appropriate error is set in the
2011-10-25 04:25:59 -04:00
* client context . */
2015-07-26 09:20:46 -04:00
int luaCreateFunction ( client * c , lua_State * lua , char * funcname , robj * body ) {
2011-10-25 04:25:59 -04:00
sds funcdef = sdsempty ( ) ;
funcdef = sdscat ( funcdef , " function " ) ;
funcdef = sdscatlen ( funcdef , funcname , 42 ) ;
2012-01-29 08:53:49 -05:00
funcdef = sdscatlen ( funcdef , " () " , 3 ) ;
2011-10-25 04:25:59 -04:00
funcdef = sdscatlen ( funcdef , body - > ptr , sdslen ( body - > ptr ) ) ;
2012-01-29 08:53:49 -05:00
funcdef = sdscatlen ( funcdef , " end " , 4 ) ;
2011-10-25 04:25:59 -04:00
2012-04-13 09:12:16 -04:00
if ( luaL_loadbuffer ( lua , funcdef , sdslen ( funcdef ) , " @user_script " ) ) {
2011-10-25 04:25:59 -04:00
addReplyErrorFormat ( c , " Error compiling script (new function): %s \n " ,
lua_tostring ( lua , - 1 ) ) ;
lua_pop ( lua , 1 ) ;
sdsfree ( funcdef ) ;
2015-07-26 17:17:55 -04:00
return C_ERR ;
2011-10-25 04:25:59 -04:00
}
sdsfree ( funcdef ) ;
if ( lua_pcall ( lua , 0 , 0 , 0 ) ) {
addReplyErrorFormat ( c , " Error running script (new function): %s \n " ,
lua_tostring ( lua , - 1 ) ) ;
lua_pop ( lua , 1 ) ;
2015-07-26 17:17:55 -04:00
return C_ERR ;
2011-10-25 04:25:59 -04:00
}
/* 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 ( server . lua_scripts ,
sdsnewlen ( funcname + 2 , 40 ) , body ) ;
2015-07-26 09:29:53 -04:00
serverAssertWithInfo ( c , NULL , retval = = DICT_OK ) ;
2011-10-25 04:25:59 -04:00
incrRefCount ( body ) ;
}
2015-07-26 17:17:55 -04:00
return C_OK ;
2011-10-25 04:25:59 -04:00
}
2015-11-05 04:36:52 -05:00
/* This is the Lua script "count" hook that we use to detect scripts timeout. */
void luaMaskCountHook ( lua_State * lua , lua_Debug * ar ) {
long long elapsed ;
UNUSED ( ar ) ;
UNUSED ( lua ) ;
elapsed = mstime ( ) - server . lua_time_start ;
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 ) ;
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
* we need to mask the client executing the script from the event loop .
* If we don ' t do that the client may disconnect and could no longer be
* here when the EVAL command will return . */
aeDeleteFileEvent ( server . el , server . lua_caller - > fd , AE_READABLE ) ;
}
if ( server . lua_timedout ) processEventsWhileBlocked ( ) ;
if ( server . lua_kill ) {
serverLog ( LL_WARNING , " Lua script killed by user with SCRIPT KILL. " ) ;
lua_pushstring ( lua , " Script killed by user with SCRIPT KILL... " ) ;
lua_error ( lua ) ;
}
}
2015-07-26 09:20:46 -04:00
void evalGenericCommand ( client * c , int evalsha ) {
2011-04-30 11:46:52 -04:00
lua_State * lua = server . lua ;
char funcname [ 43 ] ;
2011-05-01 07:07:44 -04:00
long long numkeys ;
2013-01-10 04:46:05 -05:00
int delhook = 0 , err ;
2011-05-01 07:07:44 -04:00
2015-10-29 07:39:07 -04:00
/* When we replicate whole scripts, we want the same PRNG sequence at
* every call so that our PRNG is not affected by external state . */
2011-09-23 09:40:58 -04:00
redisSrand48 ( 0 ) ;
2011-09-27 09:30:31 -04:00
/* We set this flag to zero to remember that so far no random command
* was called . This way we can allow the user to call commands like
* SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command
* is called ( otherwise the replication and AOF would end with non
* deterministic sequences ) .
*
* Thanks to this flag we ' ll raise an error every time a write command
* is called after a random command was used . */
server . lua_random_dirty = 0 ;
2011-11-18 08:10:48 -05:00
server . lua_write_dirty = 0 ;
2015-10-30 05:13:04 -04:00
server . lua_replicate_commands = server . lua_always_replicate_commands ;
2015-10-29 07:39:07 -04:00
server . lua_multi_emitted = 0 ;
2015-10-29 08:50:04 -04:00
server . lua_repl = PROPAGATE_AOF | PROPAGATE_REPL ;
2011-09-27 09:30:31 -04:00
2011-05-01 07:07:44 -04:00
/* Get the number of arguments that are keys */
2015-07-26 17:17:55 -04:00
if ( getLongLongFromObjectOrReply ( c , c - > argv [ 2 ] , & numkeys , NULL ) ! = C_OK )
2011-05-01 07:07:44 -04:00
return ;
if ( numkeys > ( c - > argc - 3 ) ) {
addReplyError ( c , " Number of keys can't be greater than number of args " ) ;
return ;
2014-06-28 22:20:44 -04:00
} else if ( numkeys < 0 ) {
addReplyError ( c , " Number of keys can't be negative " ) ;
return ;
2011-05-01 07:07:44 -04:00
}
2011-04-30 11:46:52 -04:00
/* We obtain the script SHA1, then check if this function is already
* defined into the Lua state */
funcname [ 0 ] = ' f ' ;
funcname [ 1 ] = ' _ ' ;
2011-05-13 16:02:38 -04:00
if ( ! evalsha ) {
/* Hash the code if this is an EVAL call */
2012-03-28 14:10:24 -04:00
sha1hex ( funcname + 2 , c - > argv [ 1 ] - > ptr , sdslen ( c - > argv [ 1 ] - > ptr ) ) ;
2011-05-13 16:02:38 -04:00
} else {
/* We already have the SHA if it is a EVALSHA */
int j ;
char * sha = c - > argv [ 1 ] - > ptr ;
2014-05-06 04:19:51 -04:00
/* 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 . */
2011-05-13 16:02:38 -04:00
for ( j = 0 ; j < 40 ; j + + )
2014-05-06 04:19:51 -04:00
funcname [ j + 2 ] = ( sha [ j ] > = ' A ' & & sha [ j ] < = ' Z ' ) ?
sha [ j ] + ( ' a ' - ' A ' ) : sha [ j ] ;
2011-05-13 16:02:38 -04:00
funcname [ 42 ] = ' \0 ' ;
}
2013-06-18 11:33:35 -04:00
/* Push the pcall error handler function on the stack. */
lua_getglobal ( lua , " __redis__err__handler " ) ;
2011-10-25 04:25:59 -04:00
/* Try to lookup the Lua function */
2011-04-30 11:46:52 -04:00
lua_getglobal ( lua , funcname ) ;
2013-06-18 11:33:35 -04:00
if ( lua_isnil ( lua , - 1 ) ) {
2011-10-25 05:19:15 -04:00
lua_pop ( lua , 1 ) ; /* remove the nil from the stack */
2011-05-13 16:02:38 -04:00
/* Function not defined... let's define it if we have the
2013-01-16 12:00:20 -05:00
* body of the function . If this is an EVALSHA call we can just
2011-05-13 16:02:38 -04:00
* return an error . */
if ( evalsha ) {
2013-06-18 11:33:35 -04:00
lua_pop ( lua , 1 ) ; /* remove the error handler from the stack. */
2011-05-13 16:02:38 -04:00
addReply ( c , shared . noscripterr ) ;
return ;
}
2015-07-26 17:17:55 -04:00
if ( luaCreateFunction ( c , lua , funcname , c - > argv [ 1 ] ) = = C_ERR ) {
2013-08-29 05:49:23 -04:00
lua_pop ( lua , 1 ) ; /* remove the error handler from the stack. */
/* The error is sent to the client by luaCreateFunction()
2015-07-26 17:17:55 -04:00
* itself when it returns C_ERR . */
2013-08-29 05:49:23 -04:00
return ;
}
2011-10-25 04:25:59 -04:00
/* Now the following is guaranteed to return non nil */
2011-04-30 11:46:52 -04:00
lua_getglobal ( lua , funcname ) ;
2015-07-26 09:29:53 -04:00
serverAssert ( ! lua_isnil ( lua , - 1 ) ) ;
2011-04-30 11:46:52 -04:00
}
2011-05-01 07:07:44 -04:00
/* Populate the argv and keys table accordingly to the arguments that
* EVAL received . */
luaSetGlobalArray ( lua , " KEYS " , c - > argv + 3 , numkeys ) ;
luaSetGlobalArray ( lua , " ARGV " , c - > argv + 3 + numkeys , c - > argc - 3 - numkeys ) ;
2011-05-02 18:07:41 -04:00
/* Select the right DB in the context of the Lua client */
selectDb ( server . lua_client , c - > db - > id ) ;
2014-05-20 10:11:22 -04:00
2013-12-05 10:35:32 -05:00
/* Set a hook in order to be able to stop the script execution if it
2011-05-06 11:37:03 -04:00
* is running for too much time .
* We set the hook only if the time limit is enabled as the hook will
2015-11-06 10:19:59 -05:00
* make the Lua script execution slower .
*
* If we are debugging , we set instead a " line " hook so that the
* debugger is call - back at every line executed by the script . */
2012-04-27 05:41:25 -04:00
server . lua_caller = c ;
2014-02-03 09:45:40 -05:00
server . lua_time_start = mstime ( ) ;
2012-04-27 05:41:25 -04:00
server . lua_kill = 0 ;
2015-11-06 10:19:59 -05:00
if ( server . lua_time_limit > 0 & & server . masterhost = = NULL & &
ldb . active = = 0 )
{
2011-05-06 11:37:03 -04:00
lua_sethook ( lua , luaMaskCountHook , LUA_MASKCOUNT , 100000 ) ;
2012-04-27 05:41:25 -04:00
delhook = 1 ;
2015-11-06 10:19:59 -05:00
} else if ( ldb . active ) {
lua_sethook ( server . lua , luaLdbLineHook , LUA_MASKLINE , 0 ) ;
delhook = 1 ;
2011-05-06 11:37:03 -04:00
}
2013-05-22 19:17:58 -04:00
/* At this point whether this script was never seen before or if it was
2011-04-30 11:46:52 -04:00
* already defined , we can call it . We have zero arguments and expect
* a single return value . */
2013-06-18 11:33:35 -04:00
err = lua_pcall ( lua , 0 , 1 , - 2 ) ;
2013-01-10 04:46:05 -05:00
/* Perform some cleanup that we need to do both on error and success. */
2015-11-06 10:19:59 -05:00
if ( delhook ) lua_sethook ( lua , NULL , 0 , 0 ) ; /* Disable hook */
2013-01-10 04:46:05 -05:00
if ( server . lua_timedout ) {
server . lua_timedout = 0 ;
/* Restore the readable handler that was unregistered when the
* script timeout was detected . */
aeCreateFileEvent ( server . el , c - > fd , AE_READABLE ,
readQueryFromClient , c ) ;
}
2011-11-18 08:10:48 -05:00
server . lua_caller = NULL ;
2014-05-06 06:10:22 -04:00
/* Call the Lua garbage collector from time to time to avoid a
* full cycle performed by Lua , which adds too latency .
*
* The call is performed every LUA_GC_CYCLE_PERIOD executed commands
* ( and for LUA_GC_CYCLE_PERIOD collection steps ) because calling it
* for every command uses too much CPU . */
# define LUA_GC_CYCLE_PERIOD 50
{
static long gc_count = 0 ;
gc_count + + ;
if ( gc_count = = LUA_GC_CYCLE_PERIOD ) {
lua_gc ( lua , LUA_GCSTEP , LUA_GC_CYCLE_PERIOD ) ;
gc_count = 0 ;
}
}
2011-07-13 09:38:03 -04:00
2013-01-10 04:46:05 -05:00
if ( err ) {
addReplyErrorFormat ( c , " Error running script (call to %s): %s \n " ,
funcname , lua_tostring ( lua , - 1 ) ) ;
2013-08-29 05:49:23 -04:00
lua_pop ( lua , 2 ) ; /* Consume the Lua reply and remove error handler. */
2013-01-10 04:46:05 -05:00
} else {
/* On success convert the Lua return value into Redis protocol, and
* send it to * the client . */
2013-08-29 05:49:23 -04:00
luaReplyToRedisReply ( c , lua ) ; /* Convert and consume the reply. */
lua_pop ( lua , 1 ) ; /* Remove the error handler. */
2013-01-10 04:46:05 -05:00
}
2015-10-30 04:41:04 -04:00
/* If we are using single commands replication, emit EXEC if there
* was at least a write . */
if ( server . lua_replicate_commands ) {
preventCommandPropagation ( c ) ;
if ( server . lua_multi_emitted ) {
robj * propargv [ 1 ] ;
propargv [ 0 ] = createStringObject ( " EXEC " , 4 ) ;
alsoPropagate ( server . execCommand , c - > db - > id , propargv , 1 ,
PROPAGATE_AOF | PROPAGATE_REPL ) ;
decrRefCount ( propargv [ 0 ] ) ;
}
}
2013-06-24 12:57:31 -04:00
/* 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 .
2011-07-13 09:38:03 -04:00
*
2013-06-24 12:57:31 -04:00
* For repliation , everytime 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 . */
2015-10-29 07:39:07 -04:00
if ( evalsha & & ! server . lua_replicate_commands ) {
2013-06-24 12:57:31 -04:00
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 ( server . lua_scripts , c - > argv [ 1 ] - > ptr ) ;
replicationScriptCacheAdd ( c - > argv [ 1 ] - > ptr ) ;
2015-07-26 09:29:53 -04:00
serverAssertWithInfo ( c , NULL , script ! = NULL ) ;
2013-06-24 12:57:31 -04:00
rewriteClientCommandArgument ( c , 0 ,
resetRefCount ( createStringObject ( " EVAL " , 4 ) ) ) ;
rewriteClientCommandArgument ( c , 1 , script ) ;
2015-07-27 03:41:48 -04:00
forceCommandPropagation ( c , PROPAGATE_REPL | PROPAGATE_AOF ) ;
2013-06-24 12:57:31 -04:00
}
2011-07-13 09:38:03 -04:00
}
2011-04-30 11:46:52 -04:00
}
2011-05-13 16:02:38 -04:00
2015-07-26 09:20:46 -04:00
void evalCommand ( client * c ) {
2015-11-06 10:19:59 -05:00
if ( ! ( c - > flags & CLIENT_LUA_DEBUG ) )
evalGenericCommand ( c , 0 ) ;
else
evalGenericCommandWithDebugging ( c , 0 ) ;
2011-05-13 16:02:38 -04:00
}
2015-07-26 09:20:46 -04:00
void evalShaCommand ( client * c ) {
2011-05-13 16:02:38 -04:00
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 */
addReply ( c , shared . noscripterr ) ;
return ;
}
2015-11-06 10:19:59 -05:00
if ( ! ( c - > flags & CLIENT_LUA_DEBUG ) )
evalGenericCommand ( c , 1 ) ;
else {
addReplyError ( c , " Please use EVAL instead of EVALSHA for debugging " ) ;
return ;
}
2011-05-13 16:02:38 -04:00
}
2011-09-23 09:40:58 -04:00
2015-07-26 09:20:46 -04:00
void scriptCommand ( client * c ) {
2011-10-24 16:47:00 -04:00
if ( c - > argc = = 2 & & ! strcasecmp ( c - > argv [ 1 ] - > ptr , " flush " ) ) {
scriptingReset ( ) ;
addReply ( c , shared . ok ) ;
2013-06-25 09:36:48 -04:00
replicationScriptCacheFlush ( ) ;
2013-06-25 04:56:59 -04:00
server . dirty + + ; /* Propagating this command is a good idea. */
2011-10-24 16:47:00 -04:00
} else if ( c - > argc > = 2 & & ! strcasecmp ( c - > argv [ 1 ] - > ptr , " exists " ) ) {
int j ;
addReplyMultiBulkLen ( c , c - > argc - 2 ) ;
for ( j = 2 ; j < c - > argc ; j + + ) {
if ( dictFind ( server . lua_scripts , c - > argv [ j ] - > ptr ) )
addReply ( c , shared . cone ) ;
else
addReply ( c , shared . czero ) ;
}
2011-10-25 04:25:59 -04:00
} else if ( c - > argc = = 3 & & ! strcasecmp ( c - > argv [ 1 ] - > ptr , " load " ) ) {
char funcname [ 43 ] ;
2011-10-25 05:19:15 -04:00
sds sha ;
2011-10-25 04:25:59 -04:00
funcname [ 0 ] = ' f ' ;
funcname [ 1 ] = ' _ ' ;
2012-03-28 14:10:24 -04:00
sha1hex ( funcname + 2 , c - > argv [ 2 ] - > ptr , sdslen ( c - > argv [ 2 ] - > ptr ) ) ;
2011-10-25 05:19:15 -04:00
sha = sdsnewlen ( funcname + 2 , 40 ) ;
if ( dictFind ( server . lua_scripts , sha ) = = NULL ) {
if ( luaCreateFunction ( c , server . lua , funcname , c - > argv [ 2 ] )
2015-07-26 17:17:55 -04:00
= = C_ERR ) {
2011-10-25 05:19:15 -04:00
sdsfree ( sha ) ;
return ;
}
}
2011-10-25 08:46:15 -04:00
addReplyBulkCBuffer ( c , funcname + 2 , 40 ) ;
2011-10-25 05:19:15 -04:00
sdsfree ( sha ) ;
2015-07-27 03:41:48 -04:00
forceCommandPropagation ( c , PROPAGATE_REPL | PROPAGATE_AOF ) ;
2011-11-18 08:10:48 -05:00
} else if ( c - > argc = = 2 & & ! strcasecmp ( c - > argv [ 1 ] - > ptr , " kill " ) ) {
if ( server . lua_caller = = NULL ) {
2012-10-22 04:28:54 -04:00
addReplySds ( c , sdsnew ( " -NOTBUSY No scripts in execution right now. \r \n " ) ) ;
2011-11-18 08:10:48 -05:00
} else if ( server . lua_write_dirty ) {
2013-12-05 10:35:32 -05:00
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 a hard way using the SHUTDOWN NOSAVE command. \r \n " ) ) ;
2011-11-18 08:10:48 -05:00
} else {
server . lua_kill = 1 ;
addReply ( c , shared . ok ) ;
}
2015-11-06 10:19:59 -05:00
} else if ( c - > argc = = 3 & & ! strcasecmp ( c - > argv [ 1 ] - > ptr , " debug " ) ) {
if ( clientHasPendingReplies ( c ) ) {
addReplyError ( c , " SCRIPT DEBUG must be called outside a pipeline " ) ;
return ;
}
if ( ! strcasecmp ( c - > argv [ 2 ] - > ptr , " no " ) ) {
ldbDisable ( c ) ;
} else if ( ! strcasecmp ( c - > argv [ 2 ] - > ptr , " yes " ) ) {
ldbEnable ( c ) ;
addReply ( c , shared . ok ) ;
} else if ( ! strcasecmp ( c - > argv [ 2 ] - > ptr , " sync " ) ) {
ldbEnable ( c ) ;
addReply ( c , shared . ok ) ;
c - > flags | = CLIENT_LUA_DEBUG_SYNC ;
} else {
addReplyError ( c , " Use SCRIPT DEBUG yes/async/no " ) ;
}
2011-10-24 16:47:00 -04:00
} else {
addReplyError ( c , " Unknown SCRIPT subcommand or wrong # of args. " ) ;
}
}
2015-11-05 04:36:52 -05:00
2015-11-06 10:19:59 -05:00
/* ---------------------------------------------------------------------------
* LDB : Redis Lua debugging facilities
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Initialize Lua debugger data structures. */
void ldbInit ( void ) {
ldb . fd = - 1 ;
ldb . active = 0 ;
ldb . logs = listCreate ( ) ;
listSetFreeMethod ( ldb . logs , ( void ( * ) ( void * ) ) sdsfree ) ;
ldb . src = NULL ;
2015-11-09 05:08:57 -05:00
ldb . lines = 0 ;
ldb . cbuf = sdsempty ( ) ;
2015-11-06 10:19:59 -05:00
}
/* Remove all the pending messages in the specified list. */
void ldbFlushLog ( list * log ) {
listNode * ln ;
while ( ( ln = listFirst ( log ) ) ! = NULL )
listDelNode ( log , ln ) ;
}
/* Enable debug mode of Lua scripts for this client. */
void ldbEnable ( client * c ) {
c - > flags | = CLIENT_LUA_DEBUG ;
ldbFlushLog ( ldb . logs ) ;
ldb . fd = c - > fd ;
2015-11-09 05:08:57 -05:00
ldb . step = 1 ;
2015-11-06 10:19:59 -05:00
ldb . bpcount = 0 ;
2015-11-11 04:28:35 -05:00
ldb . luabp = 0 ;
2015-11-09 05:08:57 -05:00
sdsfree ( ldb . cbuf ) ;
ldb . cbuf = sdsempty ( ) ;
2015-11-06 10:19:59 -05:00
}
void ldbDisable ( client * c ) {
c - > flags & = ~ ( CLIENT_LUA_DEBUG | CLIENT_LUA_DEBUG_SYNC ) ;
}
/* Append a log entry to the specified LDB log. */
2015-11-09 05:08:57 -05:00
void ldbLog ( sds entry ) {
listAddNodeTail ( ldb . logs , entry ) ;
2015-11-06 10:19:59 -05:00
}
2015-11-09 05:08:57 -05:00
/* Send ldb.logs to the debugging client as a multi-bulk reply
* consisting of simple strings . Log entries which include newlines have them
* replaced with spaces . The entries sent are also consumed . */
void ldbSendLogs ( void ) {
sds proto = sdsempty ( ) ;
proto = sdscatfmt ( proto , " *%i \r \n " , ( int ) listLength ( ldb . logs ) ) ;
while ( listLength ( ldb . logs ) ) {
listNode * ln = listFirst ( ldb . logs ) ;
proto = sdscatlen ( proto , " + " , 1 ) ;
sdsmapchars ( ln - > value , " \r \n " , " " , 2 ) ;
proto = sdscatsds ( proto , ln - > value ) ;
proto = sdscatlen ( proto , " \r \n " , 2 ) ;
listDelNode ( ldb . logs , ln ) ;
}
write ( ldb . fd , proto , sdslen ( proto ) ) ;
2015-11-06 10:19:59 -05:00
}
/* Start a debugging session before calling EVAL implementation.
* The techique we use is to capture the client socket file descriptor ,
* in order to perform direct I / O with it from within Lua hooks . This
* way we don ' t have to re - enter Redis in order to handle I / O .
*
* The function returns 1 if the caller should proceed to call EVAL ,
* and 0 if instead the caller should abort the operation ( this happens
* for the parent in a forked session , since it ' s up to the children
* to continue , or when fork returned an error ) .
*
* The caller should call ldbEndSession ( ) only if ldbStartSession ( )
* returned 1. */
int ldbStartSession ( client * c ) {
ldb . forked = ( c - > flags & CLIENT_LUA_DEBUG_SYNC ) = = 0 ;
if ( ldb . forked ) {
pid_t cp = fork ( ) ;
if ( cp = = - 1 ) {
addReplyError ( c , " Fork() failed: can't run EVAL in debugging mode. " ) ;
return 0 ;
} else if ( cp = = 0 ) {
/* Child */
serverLog ( LL_WARNING , " Redis forked for debugging eval " ) ;
closeListeningSockets ( 0 ) ;
} else {
/* Parent */
freeClientAsync ( c ) ; /* Close the client in the parent side. */
return 0 ;
}
}
/* Setup our debugging session. */
anetBlock ( NULL , ldb . fd ) ;
2015-11-09 05:08:57 -05:00
anetSendTimeout ( NULL , ldb . fd , 5000 ) ;
2015-11-06 10:19:59 -05:00
ldb . active = 1 ;
2015-11-09 11:01:41 -05:00
2015-11-09 05:08:57 -05:00
/* First argument of EVAL is the script itself. We split it into different
* lines since this is the way the debugger accesses the source code . */
2015-11-09 11:01:41 -05:00
sds srcstring = sdsdup ( c - > argv [ 1 ] - > ptr ) ;
size_t srclen = sdslen ( srcstring ) ;
while ( srclen & & ( srcstring [ srclen - 1 ] = = ' \n ' | |
srcstring [ srclen - 1 ] = = ' \r ' ) )
{
srcstring [ - - srclen ] = ' \0 ' ;
}
sdssetlen ( srcstring , srclen ) ;
2015-11-09 05:08:57 -05:00
ldb . src = sdssplitlen ( srcstring , sdslen ( srcstring ) , " \n " , 1 , & ldb . lines ) ;
2015-11-09 11:01:41 -05:00
sdsfree ( srcstring ) ;
2015-11-06 10:19:59 -05:00
return 1 ;
}
/* End a debugging session after the EVAL call with debugging enabled
* returned . */
void ldbEndSession ( client * c ) {
/* If it's a fork()ed session, we just exit. */
if ( ldb . forked ) {
writeToClient ( c - > fd , c , 0 ) ;
serverLog ( LL_WARNING , " Lua debugging session child exiting " ) ;
exitFromChild ( 0 ) ;
}
/* Otherwise let's restore client's state. */
anetNonBlock ( NULL , ldb . fd ) ;
2015-11-09 05:08:57 -05:00
anetSendTimeout ( NULL , ldb . fd , 0 ) ;
/* Close the client connectin after sending the final EVAL reply
* in order to signal the end of the debugging session . */
c - > flags | = CLIENT_CLOSE_AFTER_REPLY ;
/* Cleanup. */
sdsfreesplitres ( ldb . src , ldb . lines ) ;
ldb . lines = 0 ;
2015-11-06 10:19:59 -05:00
ldb . active = 0 ;
}
/* Wrapper for EVAL / EVALSHA that enables debugging, and makes sure
* that when EVAL returns , whatever happened , the session is ended . */
void evalGenericCommandWithDebugging ( client * c , int evalsha ) {
if ( ldbStartSession ( c ) ) {
evalGenericCommand ( c , evalsha ) ;
ldbEndSession ( c ) ;
} else {
ldbDisable ( c ) ;
}
}
2015-11-09 05:08:57 -05:00
/* Return a pointer to ldb.src source code line, considering line to be
* one - based , and returning a special string for out of range lines . */
char * ldbGetSourceLine ( int line ) {
int idx = line - 1 ;
if ( idx < 0 | | idx > = ldb . lines ) return " <out of range source code line> " ;
2015-11-09 11:01:41 -05:00
return ldb . src [ idx ] ;
2015-11-09 05:08:57 -05:00
}
2015-11-10 05:45:59 -05:00
/* Return true if there is a breakpoint in the specified line. */
2015-11-10 03:40:38 -05:00
int ldbIsBreakpoint ( int line ) {
2015-11-10 05:45:59 -05:00
int j ;
for ( j = 0 ; j < ldb . bpcount ; j + + )
if ( ldb . bp [ j ] = = line ) return 1 ;
return 0 ;
}
/* Add the specified breakpoint. Ignore it if we already reached the max.
* Returns 1 if the breakpoint was added ( or was already set ) . 0 if there is
* no space for the breakpoint or if the line is invalid . */
int ldbAddBreakpoint ( int line ) {
if ( line < = 0 | | line > ldb . lines ) return 0 ;
if ( ! ldbIsBreakpoint ( line ) & & ldb . bpcount ! = LDB_BREAKPOINTS_MAX ) {
ldb . bp [ ldb . bpcount + + ] = line ;
return 1 ;
}
return 0 ;
}
/* Remove the specified breakpoint, returning 1 if the operation was
* performed or 0 if there was no such breakpoint . */
int ldbDelBreakpoint ( int line ) {
int j ;
for ( j = 0 ; j < ldb . bpcount ; j + + ) {
if ( ldb . bp [ j ] = = line ) {
ldb . bpcount - - ;
memmove ( ldb . bp + j , ldb . bp + j + 1 , ldb . bpcount - j ) ;
return 1 ;
}
}
2015-11-10 03:40:38 -05:00
return 0 ;
}
2015-11-09 05:08:57 -05:00
/* Expect a valid multi-bulk command in the debugging client query buffer.
* On success the command is parsed and returned as an array of SDS strings ,
* otherwise NULL is returned and there is to read more buffer . */
sds * ldbReplParseCommand ( int * argcp ) {
sds * argv = NULL ;
int argc = 0 ;
if ( sdslen ( ldb . cbuf ) = = 0 ) return NULL ;
/* Working on a copy is simpler in this case. We can modify it freely
* for the sake of simpler parsing . */
sds copy = sdsdup ( ldb . cbuf ) ;
char * p = copy ;
/* This Redis protocol parser is a joke... just the simplest thing that
* works in this context . It is also very forgiving regarding broken
* protocol . */
/* Seek and parse *<count>\r\n. */
p = strchr ( p , ' * ' ) ; if ( ! p ) goto protoerr ;
char * plen = p + 1 ; /* Multi bulk len pointer. */
p = strstr ( p , " \r \n " ) ; if ( ! p ) goto protoerr ;
* p = ' \0 ' ; p + = 2 ;
* argcp = atoi ( plen ) ;
if ( * argcp < = 0 | | * argcp > 1024 ) goto protoerr ;
/* Parse each argument. */
argv = zmalloc ( sizeof ( sds ) * ( * argcp ) ) ;
argc = 0 ;
while ( argc < * argcp ) {
if ( * p ! = ' $ ' ) goto protoerr ;
plen = p + 1 ; /* Bulk string len pointer. */
p = strstr ( p , " \r \n " ) ; if ( ! p ) goto protoerr ;
* p = ' \0 ' ; p + = 2 ;
int slen = atoi ( plen ) ; /* Length of this arg. */
if ( slen < = 0 | | slen > 1024 ) goto protoerr ;
argv [ argc + + ] = sdsnewlen ( p , slen ) ;
p + = slen ; /* Skip the already parsed argument. */
if ( p [ 0 ] ! = ' \r ' | | p [ 1 ] ! = ' \n ' ) goto protoerr ;
p + = 2 ; /* Skip \r\n. */
}
sdsfree ( copy ) ;
return argv ;
protoerr :
sdsfreesplitres ( argv , argc ) ;
sdsfree ( copy ) ;
return NULL ;
}
2015-11-11 04:15:26 -05:00
/* Log the specified line in the Lua debugger output. */
void ldbLogSourceLine ( int lnum ) {
char * line = ldbGetSourceLine ( lnum ) ;
char * prefix ;
int bp = ldbIsBreakpoint ( lnum ) ;
int current = ldb . currentline = = lnum ;
if ( current & & bp )
prefix = " -># " ;
else if ( current )
prefix = " -> " ;
else if ( bp )
prefix = " # " ;
else
prefix = " " ;
sds thisline = sdscatprintf ( sdsempty ( ) , " %s%-3d %s " , prefix , lnum , line ) ;
ldbLog ( thisline ) ;
}
2015-11-09 11:01:41 -05:00
/* Implement the "list" command of the Lua debugger. If around is 0
* the whole file is listed , otherwise only a small portion of the file
* around the specified line is shown . When a line number is specified
* the amonut of context ( lines before / after ) is specified via the
* ' context ' argument . */
void ldbList ( int around , int context ) {
int j ;
for ( j = 1 ; j < = ldb . lines ; j + + ) {
if ( around ! = 0 & & abs ( around - j ) > context ) continue ;
2015-11-11 04:15:26 -05:00
ldbLogSourceLine ( j ) ;
2015-11-09 11:01:41 -05:00
}
}
2015-11-11 13:46:55 -05:00
/* Append an human readable representation of the Lua value at position 'idx'
* on the stack of the ' lua ' state , to the SDS string passed as argument .
* The new SDS string with the represented value attached is returned .
* Used in order to implement ldbLogStackValue ( ) .
*
* The element is not automatically removed from the stack , nor it is
* converted to a different type . */
sds ldbCatStackValue ( sds s , lua_State * lua , int idx ) {
int t = lua_type ( lua , idx ) ;
2015-11-10 03:40:38 -05:00
switch ( t ) {
case LUA_TSTRING :
2015-11-11 13:46:55 -05:00
{
size_t strl ;
char * strp = ( char * ) lua_tolstring ( lua , idx , & strl ) ;
s = sdscatrepr ( s , strp , strl ) ;
}
2015-11-10 03:40:38 -05:00
break ;
case LUA_TBOOLEAN :
2015-11-11 13:46:55 -05:00
s = sdscat ( s , lua_toboolean ( lua , idx ) ? " true " : " false " ) ;
2015-11-10 03:40:38 -05:00
break ;
case LUA_TNUMBER :
2015-11-11 13:46:55 -05:00
s = sdscatprintf ( s , " %g " , ( double ) lua_tonumber ( lua , idx ) ) ;
2015-11-10 03:40:38 -05:00
break ;
case LUA_TNIL :
2015-11-11 13:46:55 -05:00
s = sdscatlen ( s , " nil " , 3 ) ;
2015-11-10 03:40:38 -05:00
break ;
case LUA_TTABLE :
2015-11-11 13:46:55 -05:00
{
int expected_index = 1 ; /* First index we expect in an array. */
int is_array = 1 ; /* Will be set to null if check fails. */
/* Note: we create two representations at the same time, one
* assuming the table is an array , one assuming it is not . At the
* end we know what is true and select the right one . */
sds repr1 = sdsempty ( ) ;
sds repr2 = sdsempty ( ) ;
lua_pushnil ( lua ) ; /* The first key to start the iteration is nil. */
while ( lua_next ( lua , idx - 1 ) ) {
/* Test if so far the table looks like an array. */
if ( is_array & &
( lua_type ( lua , - 2 ) ! = LUA_TNUMBER | |
lua_tonumber ( lua , - 2 ) ! = expected_index ) ) is_array = 0 ;
/* Stack now: table, key, value */
/* Array repr. */
repr1 = ldbCatStackValue ( repr1 , lua , - 1 ) ;
repr1 = sdscatlen ( repr1 , " ; " , 2 ) ;
/* Full repr. */
repr2 = ldbCatStackValue ( repr2 , lua , - 2 ) ;
repr2 = sdscatlen ( repr2 , " = " , 1 ) ;
repr2 = ldbCatStackValue ( repr2 , lua , - 1 ) ;
repr2 = sdscatlen ( repr2 , " ; " , 2 ) ;
lua_pop ( lua , 1 ) ; /* Stack: table, key. Ready for next iteration. */
expected_index + + ;
}
/* Strip the last " ;" from both the representations. */
if ( sdslen ( repr1 ) ) sdsrange ( repr1 , 0 , - 3 ) ;
if ( sdslen ( repr2 ) ) sdsrange ( repr2 , 0 , - 3 ) ;
/* Select the right one and discard the other. */
s = sdscatlen ( s , " { " , 1 ) ;
s = sdscatsds ( s , is_array ? repr1 : repr2 ) ;
s = sdscatlen ( s , " } " , 1 ) ;
sdsfree ( repr1 ) ;
sdsfree ( repr2 ) ;
}
break ;
2015-11-10 03:40:38 -05:00
case LUA_TFUNCTION :
case LUA_TUSERDATA :
case LUA_TTHREAD :
case LUA_TLIGHTUSERDATA :
{
2015-11-11 13:46:55 -05:00
const void * p = lua_topointer ( lua , idx ) ;
2015-11-10 03:40:38 -05:00
char * typename = " unknown " ;
2015-11-11 13:46:55 -05:00
if ( t = = LUA_TFUNCTION ) typename = " function " ;
2015-11-10 03:40:38 -05:00
else if ( t = = LUA_TUSERDATA ) typename = " userdata " ;
else if ( t = = LUA_TTHREAD ) typename = " thread " ;
else if ( t = = LUA_TLIGHTUSERDATA ) typename = " light-userdata " ;
2015-11-11 13:46:55 -05:00
s = sdscatprintf ( s , " %s@%p " , typename , p ) ;
2015-11-10 03:40:38 -05:00
}
break ;
default :
2015-11-11 13:46:55 -05:00
s = sdscat ( s , " <unknown-lua-type> " ) ;
2015-11-10 03:40:38 -05:00
break ;
}
2015-11-11 13:46:55 -05:00
return s ;
}
/* Produce a debugger log entry representing the value of the Lua object
* currently on the top of the stack . The element is ot popped nor modified .
* Check ldbCatStackValue ( ) for the actual implementation . */
void ldbLogStackValue ( lua_State * lua , char * prefix ) {
sds s = sdsnew ( prefix ) ;
s = ldbCatStackValue ( s , lua , - 1 ) ;
2015-11-10 03:40:38 -05:00
ldbLog ( s ) ;
}
2015-11-11 04:15:26 -05:00
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 ) ;
/* Get Redis protocol from 'reply' and appends it in human readable form to
* the passed SDS string ' o ' .
*
* Note that the SDS string is passed by reference ( pointer of pointer to
* char * ) so that we can return a modified pointer , as for SDS semantics . */
char * ldbRedisProtocolToHuman ( sds * o , char * reply ) {
char * p = reply ;
switch ( * p ) {
case ' : ' : p = ldbRedisProtocolToHuman_Int ( o , reply ) ; break ;
case ' $ ' : p = ldbRedisProtocolToHuman_Bulk ( o , reply ) ; break ;
case ' + ' : p = ldbRedisProtocolToHuman_Status ( o , reply ) ; break ;
case ' - ' : p = ldbRedisProtocolToHuman_Status ( o , reply ) ; break ;
case ' * ' : p = ldbRedisProtocolToHuman_MultiBulk ( o , reply ) ; break ;
}
return p ;
}
2015-11-11 04:34:05 -05:00
/* The following functions are helpers for ldbRedisProtocolToHuman(), each
* take care of a given Redis return type . */
2015-11-11 04:15:26 -05:00
char * ldbRedisProtocolToHuman_Int ( sds * o , char * reply ) {
char * p = strchr ( reply + 1 , ' \r ' ) ;
* o = sdscatlen ( * o , reply + 1 , p - reply - 1 ) ;
return p + 2 ;
}
char * ldbRedisProtocolToHuman_Bulk ( sds * o , char * reply ) {
char * p = strchr ( reply + 1 , ' \r ' ) ;
long long bulklen ;
string2ll ( reply + 1 , p - reply - 1 , & bulklen ) ;
if ( bulklen = = - 1 ) {
* o = sdscatlen ( * o , " NULL " , 4 ) ;
return p + 2 ;
} else {
* o = sdscatrepr ( * o , p + 2 , bulklen ) ;
return p + 2 + bulklen + 2 ;
}
}
char * ldbRedisProtocolToHuman_Status ( sds * o , char * reply ) {
char * p = strchr ( reply + 1 , ' \r ' ) ;
* o = sdscatrepr ( * o , reply , p - reply ) ;
return p + 2 ;
}
char * ldbRedisProtocolToHuman_MultiBulk ( 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 ;
if ( mbulklen = = - 1 ) {
* o = sdscatlen ( * o , " NULL " , 4 ) ;
return p ;
}
* o = sdscatlen ( * o , " [ " , 1 ) ;
for ( j = 0 ; j < mbulklen ; j + + ) {
p = ldbRedisProtocolToHuman ( o , p ) ;
if ( j ! = mbulklen - 1 ) * o = sdscatlen ( * o , " , " , 1 ) ;
}
* o = sdscatlen ( * o , " ] " , 1 ) ;
return p ;
}
/* 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 . */
void ldbLogRedisReply ( char * reply , size_t maxlen ) {
sds log = sdsnew ( " <reply> " ) ;
maxlen + = sdslen ( log ) ;
ldbRedisProtocolToHuman ( & log , reply ) ;
/* Trip and add ... if the length was reached, to hint the user it's not
* the whole reply . */
if ( sdslen ( log ) > maxlen ) {
sdsrange ( log , 0 , maxlen - 1 ) ;
log = sdscatlen ( log , " ... " , 4 ) ;
}
ldbLog ( log ) ;
}
2015-11-10 03:40:38 -05:00
/* Implements the "print" command of the Lua debugger. It scans for Lua
* var " varname " starting from the current stack frame up to the top stack
* frame . The first matching variable is printed . */
2015-11-10 05:45:59 -05:00
void ldbPrint ( lua_State * lua , char * varname ) {
2015-11-10 03:40:38 -05:00
lua_Debug ar ;
2015-11-10 05:45:59 -05:00
2015-11-10 03:40:38 -05:00
int l = 0 ; /* Stack level. */
while ( lua_getstack ( lua , l , & ar ) ! = 0 ) {
l + + ;
const char * name ;
int i = 1 ; /* Variable index. */
while ( ( name = lua_getlocal ( lua , & ar , i ) ) ! = NULL ) {
i + + ;
if ( strcmp ( varname , name ) = = 0 ) {
2015-11-11 04:15:26 -05:00
ldbLogStackValue ( lua , " <value> " ) ;
2015-11-11 13:46:55 -05:00
lua_pop ( lua , 1 ) ;
2015-11-10 03:40:38 -05:00
return ;
} else {
lua_pop ( lua , 1 ) ; /* Discard the var name on the stack. */
}
}
}
2015-11-11 04:43:00 -05:00
/* Let's try with global vars in two selected cases */
if ( ! strcmp ( varname , " ARGV " ) | | ! strcmp ( varname , " KEYS " ) ) {
lua_getglobal ( lua , varname ) ;
ldbLogStackValue ( lua , " <value> " ) ;
lua_pop ( lua , 1 ) ;
} else {
ldbLog ( sdsnew ( " No such variable. " ) ) ;
}
2015-11-10 03:40:38 -05:00
}
2015-11-10 05:45:59 -05:00
/* Implements the break command to list, add and remove breakpoints. */
void ldbBreak ( sds * argv , int argc ) {
if ( argc = = 1 ) {
if ( ldb . bpcount = = 0 ) {
ldbLog ( sdsnew ( " No breakpoints set. Use 'b <line>' to add one. " ) ) ;
return ;
} else {
ldbLog ( sdscatfmt ( sdsempty ( ) , " %i breakpoints set: " , ldb . bpcount ) ) ;
int j ;
2015-11-11 04:15:26 -05:00
for ( j = 0 ; j < ldb . bpcount ; j + + )
ldbLogSourceLine ( ldb . bp [ j ] ) ;
2015-11-10 05:45:59 -05:00
}
} else {
int j ;
for ( j = 1 ; j < argc ; j + + ) {
char * arg = argv [ j ] ;
long line ;
if ( ! string2l ( arg , sdslen ( arg ) , & line ) ) {
ldbLog ( sdscatfmt ( sdsempty ( ) , " Invalid argument:'%s' " , arg ) ) ;
} else {
if ( line = = 0 ) {
ldb . bpcount = 0 ;
ldbLog ( sdsnew ( " All breakpoints removed. " ) ) ;
} else if ( line > 0 ) {
if ( ldb . bpcount = = LDB_BREAKPOINTS_MAX ) {
ldbLog ( sdsnew ( " Too many breakpoints set. " ) ) ;
} else if ( ldbAddBreakpoint ( line ) ) {
ldbList ( line , 1 ) ;
} else {
ldbLog ( sdsnew ( " Wrong line number. " ) ) ;
}
} else if ( line < 0 ) {
2015-11-11 17:07:57 -05:00
if ( ldbDelBreakpoint ( - line ) )
2015-11-10 05:45:59 -05:00
ldbLog ( sdsnew ( " Breakpoint removed. " ) ) ;
else
ldbLog ( sdsnew ( " No breakpoint in the specified line. " ) ) ;
}
}
}
}
}
2015-11-11 04:15:26 -05:00
/* Implements the Lua debugger "eval" command. It just compiles the user
* passed fragment of code and executes it , showing the result left on
* the stack . */
void ldbEval ( lua_State * lua , sds * argv , int argc ) {
/* Glue the script together if it is composed of multiple arguments. */
sds code = sdsjoinsds ( argv + 1 , argc - 1 , " " , 1 ) ;
2015-11-11 16:29:56 -05:00
sds expr = sdscatsds ( sdsnew ( " return " ) , code ) ;
2015-11-11 04:15:26 -05:00
2015-11-11 16:29:56 -05:00
/* Try to compile it as an expression, prepending "return ". */
if ( luaL_loadbuffer ( lua , expr , sdslen ( expr ) , " @ldb_eval " ) ) {
2015-11-11 04:15:26 -05:00
lua_pop ( lua , 1 ) ;
2015-11-11 16:29:56 -05:00
/* Failed? Try as a statement. */
if ( luaL_loadbuffer ( lua , code , sdslen ( code ) , " @ldb_eval " ) ) {
ldbLog ( sdscatfmt ( sdsempty ( ) , " <error> %s " , lua_tostring ( lua , - 1 ) ) ) ;
lua_pop ( lua , 1 ) ;
sdsfree ( code ) ;
return ;
}
2015-11-11 04:15:26 -05:00
}
2015-11-11 16:29:56 -05:00
/* Call it. */
2015-11-11 04:15:26 -05:00
sdsfree ( code ) ;
2015-11-11 16:29:56 -05:00
sdsfree ( expr ) ;
2015-11-11 04:15:26 -05:00
if ( lua_pcall ( lua , 0 , 1 , 0 ) ) {
ldbLog ( sdscatfmt ( sdsempty ( ) , " <error> %s " , lua_tostring ( lua , - 1 ) ) ) ;
lua_pop ( lua , 1 ) ;
return ;
}
ldbLogStackValue ( lua , " <retval> " ) ;
2015-11-11 13:46:55 -05:00
lua_pop ( lua , 1 ) ;
2015-11-11 04:15:26 -05:00
}
2015-11-11 16:56:38 -05:00
/* Implement the debugger "redis" command. We use a trick in order to make
* the implementation very simple : we just call the Lua redis . call ( ) command
* implementation , with ldb . step enabled , so as a side effect the Redis command
* and its reply are logged . */
void ldbRedis ( lua_State * lua , sds * argv , int argc ) {
int j , saved_rc = server . lua_replicate_commands ;
lua_getglobal ( lua , " redis " ) ;
lua_pushstring ( lua , " call " ) ;
lua_gettable ( lua , - 2 ) ; /* Stack: redis, redis.call */
for ( j = 1 ; j < argc ; j + + )
lua_pushlstring ( lua , argv [ j ] , sdslen ( argv [ j ] ) ) ;
ldb . step = 1 ; /* Force redis.call() to log. */
server . lua_replicate_commands = 1 ;
lua_pcall ( lua , argc - 1 , 1 , 0 ) ; /* Stack: redis, result */
ldb . step = 0 ; /* Disable logging. */
server . lua_replicate_commands = saved_rc ;
lua_pop ( lua , 2 ) ; /* Discard the result and clean the stack. */
}
2015-11-09 05:08:57 -05:00
/* Read debugging commands from client. */
2015-11-10 05:45:59 -05:00
void ldbRepl ( lua_State * lua ) {
2015-11-09 05:08:57 -05:00
sds * argv ;
int argc ;
/* We continue processing commands until a command that should return
* to the Lua interpreter is found . */
while ( 1 ) {
while ( ( argv = ldbReplParseCommand ( & argc ) ) = = NULL ) {
char buf [ 1024 ] ;
int nread = read ( ldb . fd , buf , sizeof ( buf ) ) ;
if ( nread < = 0 ) {
/* Make sure the script runs without user input since the
* client is no longer connected . */
ldb . step = 0 ;
ldb . bpcount = 0 ;
return ;
}
ldb . cbuf = sdscatlen ( ldb . cbuf , buf , nread ) ;
}
/* Flush the old buffer. */
sdsfree ( ldb . cbuf ) ;
ldb . cbuf = sdsempty ( ) ;
/* Execute the command. */
2015-11-11 04:15:26 -05:00
if ( ! strcasecmp ( argv [ 0 ] , " h " ) | | ! strcasecmp ( argv [ 0 ] , " help " ) ) {
2015-11-09 11:01:41 -05:00
ldbLog ( sdsnew ( " Redis Lua debugger help: " ) ) ;
2015-11-12 02:33:52 -05:00
ldbLog ( sdsnew ( " [h]elp Show this help. " ) ) ;
ldbLog ( sdsnew ( " [s]tep Run current line and stop again. " ) ) ;
ldbLog ( sdsnew ( " [n]ext Alias for step. " ) ) ;
ldbLog ( sdsnew ( " [c]continue Run till next breakpoint. " ) ) ;
ldbLog ( sdsnew ( " [l]list [line] List source code, around [line] if specified " ) ) ;
ldbLog ( sdsnew ( " you can use another arg for context size. " ) ) ;
ldbLog ( sdsnew ( " [p]rint <var> Show the value of the specified variable. " ) ) ;
ldbLog ( sdsnew ( " [b]eark Show all breakpoints. " ) ) ;
ldbLog ( sdsnew ( " [b]eark <line> Add a breakpoint to the specified line. " ) ) ;
ldbLog ( sdsnew ( " [b]eark -<line> Remove breakpoint from the specified line. " ) ) ;
ldbLog ( sdsnew ( " [b]eark 0 Remove all breakpoints. " ) ) ;
ldbLog ( sdsnew ( " [e]eval <code> Execute some Lua code (in a different callframe). " ) ) ;
ldbLog ( sdsnew ( " [r]edis <cmd> Execute a Redis command. " ) ) ;
ldbLog ( sdsnew ( " " ) ) ;
ldbLog ( sdsnew ( " Debugger functions you can call from Lua scripts: " ) ) ;
ldbLog ( sdsnew ( " redis.debug() Produce logs in the debugger console. " ) ) ;
ldbLog ( sdsnew ( " redis.breakpoint() Stop execution like if there was a breakpoing. " ) ) ;
ldbLog ( sdsnew ( " in the next line of code. " ) ) ;
2015-11-09 05:08:57 -05:00
ldbSendLogs ( ) ;
2015-11-09 11:01:41 -05:00
} else if ( ! strcasecmp ( argv [ 0 ] , " s " ) | | ! strcasecmp ( argv [ 0 ] , " step " ) | |
! strcasecmp ( argv [ 0 ] , " n " ) | | ! strcasecmp ( argv [ 0 ] , " next " ) ) {
2015-11-09 05:08:57 -05:00
ldb . step = 1 ;
break ;
} else if ( ! strcasecmp ( argv [ 0 ] , " c " ) | | ! strcasecmp ( argv [ 0 ] , " continue " ) ) {
break ;
2015-11-11 04:15:26 -05:00
} else if ( ! strcasecmp ( argv [ 0 ] , " b " ) | | ! strcasecmp ( argv [ 0 ] , " break " ) ) {
2015-11-10 05:45:59 -05:00
ldbBreak ( argv , argc ) ;
ldbSendLogs ( ) ;
2015-11-11 04:15:26 -05:00
} else if ( ! strcasecmp ( argv [ 0 ] , " e " ) | | ! strcasecmp ( argv [ 0 ] , " eval " ) ) {
ldbEval ( lua , argv , argc ) ;
ldbSendLogs ( ) ;
2015-11-11 16:56:38 -05:00
} else if ( argc > 1 & &
( ! strcasecmp ( argv [ 0 ] , " r " ) | | ! strcasecmp ( argv [ 0 ] , " redis " ) ) ) {
ldbRedis ( lua , argv , argc ) ;
ldbSendLogs ( ) ;
2015-11-10 03:40:38 -05:00
} else if ( argc = = 2 & &
( ! strcasecmp ( argv [ 0 ] , " p " ) | | ! strcasecmp ( argv [ 0 ] , " print " ) ) )
{
2015-11-10 05:45:59 -05:00
ldbPrint ( lua , argv [ 1 ] ) ;
2015-11-10 03:40:38 -05:00
ldbSendLogs ( ) ;
2015-11-09 11:01:41 -05:00
} else if ( ! strcasecmp ( argv [ 0 ] , " l " ) | | ! strcasecmp ( argv [ 0 ] , " list " ) ) {
int around = 0 , ctx = 5 ;
if ( argc > 1 ) around = atoi ( argv [ 1 ] ) ;
if ( argc > 2 ) ctx = atoi ( argv [ 2 ] ) ;
ldbList ( around , ctx ) ;
ldbSendLogs ( ) ;
2015-11-09 05:08:57 -05:00
} else {
2015-11-11 04:15:26 -05:00
ldbLog ( sdsnew ( " <error> Unknown Redis Lua debugger command or "
2015-11-10 03:40:38 -05:00
" wrong number of arguments. " ) ) ;
2015-11-09 05:08:57 -05:00
ldbSendLogs ( ) ;
}
/* Free the command vector. */
sdsfreesplitres ( argv , argc ) ;
}
/* Free the current command argv if we break inside the while loop. */
sdsfreesplitres ( argv , argc ) ;
}
2015-11-06 10:19:59 -05:00
/* This is the core of our Lua debugger, called each time Lua is about
* to start executing a new line . */
void luaLdbLineHook ( lua_State * lua , lua_Debug * ar ) {
lua_getstack ( lua , 0 , ar ) ;
lua_getinfo ( lua , " Sl " , ar ) ;
2015-11-09 05:08:57 -05:00
if ( strstr ( ar - > short_src , " user_script " ) = = NULL ) return ;
2015-11-12 02:33:52 -05:00
ldb . currentline = ar - > currentline ;
int bp = ldbIsBreakpoint ( ldb . currentline ) | | ldb . luabp ;
2015-11-11 04:15:26 -05:00
if ( ldb . step | | bp ) {
2015-11-11 04:28:35 -05:00
char * reason = " step over " ;
if ( bp ) reason = ldb . luabp ? " redis.breakpoint() called " :
" break point " ;
2015-11-09 05:08:57 -05:00
ldb . step = 0 ;
2015-11-11 04:28:35 -05:00
ldb . luabp = 0 ;
2015-11-11 04:15:26 -05:00
ldbLog ( sdscatprintf ( sdsempty ( ) ,
" * Stopped at %d, stop reason = %s " ,
ldb . currentline , reason ) ) ;
ldbLogSourceLine ( ldb . currentline ) ;
2015-11-09 05:08:57 -05:00
ldbSendLogs ( ) ;
2015-11-10 05:45:59 -05:00
ldbRepl ( lua ) ;
2015-11-09 05:08:57 -05:00
}
2015-11-06 10:19:59 -05:00
}