2011-04-30 11:46:52 -04:00
# include "redis.h"
# include "sha1.h"
2011-09-23 09:40:58 -04:00
# include "rand.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 ) ;
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
* clients , it is trvial to implement the redis ( ) lua function .
*
* 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
* need the introduction of a full Redis internals API . Basically the script
* 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
* The reply string can be altered during the parsing as it is discared
* after the conversion is completed .
*
* 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 ) {
case ' : ' :
p = redisProtocolToLuaType_Int ( lua , reply ) ;
break ;
case ' $ ' :
p = redisProtocolToLuaType_Bulk ( lua , reply ) ;
break ;
case ' + ' :
p = redisProtocolToLuaType_Status ( lua , reply ) ;
break ;
2011-05-01 09:26:47 -04:00
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 ;
}
2011-05-01 17:43:10 -04:00
void luaPushError ( lua_State * lua , char * error ) {
lua_newtable ( lua ) ;
lua_pushstring ( lua , " err " ) ;
lua_pushstring ( lua , error ) ;
lua_settable ( lua , - 3 ) ;
}
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) */
}
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 ;
robj * * argv ;
redisClient * c = server . lua_client ;
sds reply ;
2011-05-01 08:47:52 -04:00
/* Build the arguments vector */
2011-04-30 16:29:21 -04:00
argv = zmalloc ( sizeof ( robj * ) * argc ) ;
2011-05-01 17:43:10 -04:00
for ( j = 0 ; j < argc ; j + + ) {
if ( ! lua_isstring ( lua , j + 1 ) ) break ;
2011-05-01 08:47:52 -04:00
argv [ j ] = createStringObject ( ( char * ) lua_tostring ( lua , j + 1 ) ,
lua_strlen ( lua , j + 1 ) ) ;
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 - - ;
}
zfree ( argv ) ;
luaPushError ( lua ,
" Lua redis() command arguments must be strings or integers " ) ;
return 1 ;
}
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 ;
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
}
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. */
2011-09-27 07:57:10 -04:00
if ( cmd - > flags & REDIS_CMD_NOSCRIPT ) {
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 . */
if ( cmd - > flags & REDIS_CMD_WRITE ) {
if ( server . lua_random_dirty ) {
luaPushError ( lua ,
" Write commands not allowed after non deterministic commands " ) ;
goto cleanup ;
} else if ( server . masterhost & & server . repl_slave_ro & &
! ( server . lua_caller - > flags & REDIS_MASTER ) )
{
luaPushError ( lua , shared . roslaveerr - > ptr ) ;
goto cleanup ;
} else if ( server . stop_writes_on_bgsave_err & &
server . saveparamslen > 0 & &
server . lastbgsave_status = = REDIS_ERR )
{
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 & &
( cmd - > flags & REDIS_CMD_DENYOOM ) )
{
if ( freeMemoryIfNeeded ( ) = = REDIS_ERR ) {
luaPushError ( lua , shared . oomerr - > ptr ) ;
goto cleanup ;
}
2011-09-27 09:30:31 -04:00
}
if ( cmd - > flags & REDIS_CMD_RANDOM ) server . lua_random_dirty = 1 ;
2011-11-18 08:10:48 -05:00
if ( cmd - > flags & REDIS_CMD_WRITE ) server . lua_write_dirty = 1 ;
2011-09-27 09:30:31 -04:00
2011-09-27 07:57:10 -04:00
/* Run the command */
2012-02-02 10:30:52 -05:00
c - > cmd = cmd ;
call ( c , REDIS_CALL_SLOWLOG | REDIS_CALL_STATS ) ;
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 . */
reply = sdsempty ( ) ;
if ( c - > bufpos ) {
2011-04-30 21:12:53 -04:00
reply = sdscatlen ( reply , c - > buf , c - > bufpos ) ;
2011-04-30 16:29:21 -04:00
c - > bufpos = 0 ;
}
while ( listLength ( c - > reply ) ) {
robj * o = listNodeValue ( listFirst ( c - > reply ) ) ;
2011-05-02 17:04:08 -04:00
reply = sdscatlen ( reply , o - > ptr , sdslen ( o - > ptr ) ) ;
2011-04-30 16:29:21 -04:00
listDelNode ( c - > reply , listFirst ( c - > reply ) ) ;
}
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 ) ;
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 . */
if ( ( cmd - > flags & REDIS_CMD_SORT_FOR_SCRIPT ) & &
( reply [ 0 ] = = ' * ' & & reply [ 1 ] ! = ' - ' ) ) {
2012-02-01 09:22:28 -05:00
/* Skip this step if command is SORT but output was already sorted */
if ( cmd - > proc ! = sortCommand | | server . sort_dontsort )
luaSortArray ( lua ) ;
2012-01-31 10:09:21 -05:00
}
2011-04-30 21:12:53 -04:00
sdsfree ( reply ) ;
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 . */
for ( j = 0 ; j < c - > argc ; j + + )
decrRefCount ( c - > argv [ j ] ) ;
zfree ( c - > argv ) ;
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 . */
lua_pushstring ( lua , " err " ) ;
lua_gettable ( lua , - 2 ) ;
return lua_error ( lua ) ;
}
2011-04-30 16:29:21 -04:00
return 1 ;
}
2011-10-20 10:02:23 -04:00
int luaRedisCallCommand ( lua_State * lua ) {
return luaRedisGenericCommand ( lua , 1 ) ;
}
int luaRedisPCallCommand ( lua_State * lua ) {
return luaRedisGenericCommand ( lua , 0 ) ;
}
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 ) {
luaPushError ( lua , " redis.log() requires two arguments or more. " ) ;
return 1 ;
} else if ( ! lua_isnumber ( lua , - argc ) ) {
2011-05-16 12:36:07 -04:00
luaPushError ( lua , " First argument must be a number (log level). " ) ;
2011-05-16 12:32:03 -04:00
return 1 ;
}
level = lua_tonumber ( lua , - argc ) ;
2011-05-16 12:36:07 -04:00
if ( level < REDIS_DEBUG | | level > REDIS_WARNING ) {
2011-05-16 12:32:03 -04:00
luaPushError ( lua , " Invalid debug level. " ) ;
return 1 ;
}
/* 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 ) ;
}
}
redisLogRaw ( level , log ) ;
sdsfree ( log ) ;
return 0 ;
}
2011-05-06 11:21:27 -04:00
void luaMaskCountHook ( lua_State * lua , lua_Debug * ar ) {
long long elapsed ;
REDIS_NOTUSED ( ar ) ;
2011-10-27 08:49:10 -04:00
REDIS_NOTUSED ( lua ) ;
2011-05-06 11:21:27 -04:00
elapsed = ( ustime ( ) / 1000 ) - server . lua_time_start ;
2011-10-27 08:49:10 -04:00
if ( elapsed > = server . lua_time_limit & & server . lua_timedout = = 0 ) {
2011-11-18 08:10:48 -05:00
redisLog ( REDIS_WARNING , " Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command. " , elapsed ) ;
2011-10-27 08:49:10 -04:00
server . lua_timedout = 1 ;
2011-11-18 08:10:48 -05:00
/* 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 ) ;
2011-05-06 11:21:27 -04:00
}
2011-10-27 08:49:10 -04:00
if ( server . lua_timedout )
aeProcessEvents ( server . el , AE_FILE_EVENTS | AE_DONT_WAIT ) ;
2011-11-18 08:10:48 -05:00
if ( server . lua_kill ) {
redisLog ( REDIS_WARNING , " Lua script killed by user with SCRIPT KILL. " ) ;
lua_pushstring ( lua , " Script killed by user with SCRIPT KILL... " ) ;
lua_error ( lua ) ;
}
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 ) ;
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 ) ;
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 ) ;
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
}
2011-10-24 16:47:00 -04:00
/* Initialize the scripting environment.
* It is possible to call this function to reset the scripting environment
* assuming that we call scriptingRelease ( ) before .
* See scriptingReset ( ) for more information . */
2011-04-30 11:46:52 -04:00
void scriptingInit ( void ) {
lua_State * lua = lua_open ( ) ;
2011-09-27 12:46:23 -04:00
luaLoadLibraries ( 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 . */
server . lua_scripts = dictCreate ( & dbDictType , NULL ) ;
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 ) ;
lua_pushstring ( lua , " LOG_DEBUG " ) ;
lua_pushnumber ( lua , REDIS_DEBUG ) ;
lua_settable ( lua , - 3 ) ;
lua_pushstring ( lua , " LOG_VERBOSE " ) ;
lua_pushnumber ( lua , REDIS_VERBOSE ) ;
lua_settable ( lua , - 3 ) ;
lua_pushstring ( lua , " LOG_NOTICE " ) ;
lua_pushnumber ( lua , REDIS_NOTICE ) ;
lua_settable ( lua , - 3 ) ;
lua_pushstring ( lua , " LOG_WARNING " ) ;
lua_pushnumber ( lua , REDIS_WARNING ) ;
lua_settable ( lua , - 3 ) ;
/* 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 " ) ;
2012-02-01 09:22:28 -05:00
/* Add a helper funciton that we use to sort the multi bulk output of non
* deterministic commands , when containing ' false ' elements . */
{
char * compare_func = " function __redis__compare_helper(a,b) \n "
" if a == false then a = '' end \n "
" if b == false then b = '' end \n "
" return a<b \n "
" end \n " ;
luaL_loadbuffer ( lua , compare_func , strlen ( compare_func ) , " cmp_func_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 ) ;
server . lua_client - > flags | = REDIS_LUA_CLIENT ;
}
2011-04-30 16:29:21 -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 ( ) ;
scriptingInit ( ) ;
}
2011-04-30 11:46:52 -04:00
/* Hash the scripit into a SHA1 digest. We use this as Lua function name.
* Digest should point to a 41 bytes buffer : 40 for SHA1 converted into an
* hexadecimal number , plus 1 byte for null term . */
void hashScript ( 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 ' ;
}
void luaReplyToRedisReply ( redisClient * c , lua_State * lua ) {
2011-05-13 10:42:43 -04:00
int t = lua_type ( lua , - 1 ) ;
2011-04-30 11:46:52 -04:00
switch ( t ) {
case LUA_TSTRING :
2011-05-13 10:42:43 -04:00
addReplyBulkCBuffer ( c , ( char * ) lua_tostring ( lua , - 1 ) , lua_strlen ( lua , - 1 ) ) ;
2011-04-30 11:46:52 -04:00
break ;
case LUA_TBOOLEAN :
2011-05-13 10:42:43 -04:00
addReply ( c , lua_toboolean ( lua , - 1 ) ? shared . cone : shared . nullbulk ) ;
2011-04-30 11:46:52 -04:00
break ;
case LUA_TNUMBER :
2011-05-13 10:42:43 -04:00
addReplyLongLong ( c , ( long long ) lua_tonumber ( lua , - 1 ) ) ;
2011-04-30 11:46:52 -04:00
break ;
2011-05-01 08:47:52 -04:00
case LUA_TTABLE :
2011-05-02 04:08:26 -04:00
/* 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 elment table with ' ok ' field */
2011-05-01 08:47:52 -04:00
lua_pushstring ( lua , " err " ) ;
lua_gettable ( lua , - 2 ) ;
t = lua_type ( lua , - 1 ) ;
if ( t = = LUA_TSTRING ) {
2011-05-24 13:43:11 -04:00
sds err = sdsnew ( lua_tostring ( lua , - 1 ) ) ;
sdsmapchars ( err , " \r \n " , " " , 2 ) ;
addReplySds ( c , sdscatprintf ( sdsempty ( ) , " -%s \r \n " , err ) ) ;
sdsfree ( err ) ;
2011-05-02 04:08:26 -04:00
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 ) {
2011-05-24 13:43:11 -04:00
sds ok = sdsnew ( lua_tostring ( lua , - 1 ) ) ;
sdsmapchars ( ok , " \r \n " , " " , 2 ) ;
addReplySds ( c , sdscatprintf ( sdsempty ( ) , " +%s \r \n " , ok ) ) ;
sdsfree ( ok ) ;
2011-05-01 08:47:52 -04:00
lua_pop ( lua , 1 ) ;
} else {
void * replylen = addDeferredMultiBulkLength ( c ) ;
int j = 1 , mbulklen = 0 ;
2011-05-02 04:08:26 -04:00
lua_pop ( lua , 1 ) ; /* Discard the 'ok' field value we popped */
2011-05-01 08:47:52 -04:00
while ( 1 ) {
lua_pushnumber ( lua , j + + ) ;
lua_gettable ( lua , - 2 ) ;
t = lua_type ( lua , - 1 ) ;
if ( t = = LUA_TNIL ) {
lua_pop ( lua , 1 ) ;
break ;
}
2011-05-13 10:42:43 -04:00
luaReplyToRedisReply ( c , lua ) ;
mbulklen + + ;
2011-05-01 08:47:52 -04:00
}
setDeferredMultiBulkLength ( c , replylen , mbulklen ) ;
}
break ;
2011-04-30 11:46:52 -04:00
default :
addReply ( c , shared . nullbulk ) ;
}
lua_pop ( lua , 1 ) ;
}
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 ) ;
}
2011-10-25 04:25:59 -04:00
/* Define a lua function with the specified function name and body.
* The function name musts be a 2 characters long string , since all the
* functions we defined in the Lua context are in the form :
*
* f_ < hex sha1 sum >
*
* On success REDIS_OK is returned , and nothing is left on the Lua stack .
* On error REDIS_ERR is returned and an appropriate error is set in the
* client context . */
int luaCreateFunction ( redisClient * c , lua_State * lua , char * funcname , robj * body ) {
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
if ( luaL_loadbuffer ( lua , funcdef , sdslen ( funcdef ) , " func definition " ) ) {
addReplyErrorFormat ( c , " Error compiling script (new function): %s \n " ,
lua_tostring ( lua , - 1 ) ) ;
lua_pop ( lua , 1 ) ;
sdsfree ( funcdef ) ;
return REDIS_ERR ;
}
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 ) ;
return REDIS_ERR ;
}
/* 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 ) ;
redisAssertWithInfo ( c , NULL , retval = = DICT_OK ) ;
incrRefCount ( body ) ;
}
return REDIS_OK ;
}
2011-05-13 16:02:38 -04:00
void evalGenericCommand ( redisClient * 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 ;
2011-09-23 09:40:58 -04:00
/* We want the same PRNG sequence at every call so that our PRNG is
* not affected by external state . */
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 ;
2011-09-27 09:30:31 -04:00
2011-05-01 07:07:44 -04:00
/* Get the number of arguments that are keys */
if ( getLongLongFromObjectOrReply ( c , c - > argv [ 2 ] , & numkeys , NULL ) ! = REDIS_OK )
return ;
if ( numkeys > ( c - > argc - 3 ) ) {
addReplyError ( c , " Number of keys can't be greater than number of args " ) ;
return ;
}
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 */
hashScript ( funcname + 2 , c - > argv [ 1 ] - > ptr , sdslen ( c - > argv [ 1 ] - > ptr ) ) ;
} else {
/* We already have the SHA if it is a EVALSHA */
int j ;
char * sha = c - > argv [ 1 ] - > ptr ;
for ( j = 0 ; j < 40 ; j + + )
funcname [ j + 2 ] = tolower ( sha [ j ] ) ;
funcname [ 42 ] = ' \0 ' ;
}
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 ) ;
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
* body of the funciton . If this is an EVALSHA call we can just
* return an error . */
if ( evalsha ) {
addReply ( c , shared . noscripterr ) ;
return ;
}
2011-10-25 04:25:59 -04:00
if ( luaCreateFunction ( c , lua , funcname , c - > argv [ 1 ] ) = = REDIS_ERR ) return ;
/* Now the following is guaranteed to return non nil */
2011-04-30 11:46:52 -04:00
lua_getglobal ( lua , funcname ) ;
2011-10-25 04:25:59 -04:00
redisAssert ( ! 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 ) ;
2011-04-30 11:46:52 -04:00
2011-05-06 11:37:03 -04:00
/* Set an hook in order to be able to stop the script execution if it
* is running for too much time .
* We set the hook only if the time limit is enabled as the hook will
* make the Lua script execution slower . */
2011-10-27 08:49:10 -04:00
if ( server . lua_time_limit > 0 & & server . masterhost = = NULL ) {
2011-05-06 11:37:03 -04:00
lua_sethook ( lua , luaMaskCountHook , LUA_MASKCOUNT , 100000 ) ;
} else {
lua_sethook ( lua , luaMaskCountHook , 0 , 0 ) ;
}
2011-04-30 11:46:52 -04:00
/* At this point whatever this script was never seen before or if it was
* already defined , we can call it . We have zero arguments and expect
* a single return value . */
2011-11-18 08:10:48 -05:00
server . lua_caller = c ;
server . lua_time_start = ustime ( ) / 1000 ;
server . lua_kill = 0 ;
2011-04-30 11:46:52 -04:00
if ( lua_pcall ( lua , 0 , 1 , 0 ) ) {
2011-11-18 08:10:48 -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 ) ;
}
server . lua_caller = NULL ;
2011-05-02 18:07:41 -04:00
selectDb ( c , server . lua_client - > db - > id ) ; /* set DB ID from Lua client */
2011-04-30 11:46:52 -04:00
addReplyErrorFormat ( c , " Error running script (call to %s): %s \n " ,
funcname , lua_tostring ( lua , - 1 ) ) ;
lua_pop ( lua , 1 ) ;
2011-05-07 05:32:37 -04:00
lua_gc ( lua , LUA_GCCOLLECT , 0 ) ;
2011-04-30 11:46:52 -04:00
return ;
}
2011-10-27 08:49:10 -04:00
server . lua_timedout = 0 ;
2011-11-18 08:10:48 -05:00
server . lua_caller = NULL ;
2011-05-02 18:07:41 -04:00
selectDb ( c , server . lua_client - > db - > id ) ; /* set DB ID from Lua client */
2011-04-30 11:46:52 -04:00
luaReplyToRedisReply ( c , lua ) ;
2011-05-07 05:32:37 -04:00
lua_gc ( lua , LUA_GCSTEP , 1 ) ;
2011-07-13 09:38:03 -04:00
/* If we have slaves attached we want to replicate this command as
* EVAL instead of EVALSHA . We do this also in the AOF as currently there
* is no easy way to propagate a command in a different way in the AOF
* and in the replication link .
*
* IMPROVEMENT POSSIBLE :
* 1 ) Replicate this command as EVALSHA in the AOF .
* 2 ) Remember what slave already received a given script , and replicate
* the EVALSHA against this slaves when possible .
*/
if ( evalsha ) {
robj * script = dictFetchValue ( server . lua_scripts , c - > argv [ 1 ] - > ptr ) ;
2011-10-04 12:43:03 -04:00
redisAssertWithInfo ( c , NULL , script ! = NULL ) ;
2011-07-13 09:38:03 -04:00
rewriteClientCommandArgument ( c , 0 ,
resetRefCount ( createStringObject ( " EVAL " , 4 ) ) ) ;
rewriteClientCommandArgument ( c , 1 , script ) ;
}
2011-04-30 11:46:52 -04:00
}
2011-05-13 16:02:38 -04:00
void evalCommand ( redisClient * c ) {
evalGenericCommand ( c , 0 ) ;
}
void evalShaCommand ( redisClient * c ) {
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 ;
}
evalGenericCommand ( c , 1 ) ;
}
2011-09-23 09:40:58 -04:00
/* 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 ;
}
2011-10-24 16:47:00 -04:00
/* ---------------------------------------------------------------------------
* SCRIPT command for script environment introspection and control
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void scriptCommand ( redisClient * c ) {
if ( c - > argc = = 2 & & ! strcasecmp ( c - > argv [ 1 ] - > ptr , " flush " ) ) {
scriptingReset ( ) ;
addReply ( c , shared . ok ) ;
server . dirty + + ; /* Replicating this command is a good idea. */
} 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 ] = ' _ ' ;
hashScript ( 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 ] )
= = REDIS_ERR ) {
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 ) ;
2011-11-18 08:10:48 -05:00
} else if ( c - > argc = = 2 & & ! strcasecmp ( c - > argv [ 1 ] - > ptr , " kill " ) ) {
if ( server . lua_caller = = NULL ) {
addReplyError ( c , " No scripts in execution right now. " ) ;
} else if ( server . lua_write_dirty ) {
addReplyError ( c , " Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in an hard way using the SHUTDOWN NOSAVE command. " ) ;
} else {
server . lua_kill = 1 ;
addReply ( c , shared . ok ) ;
}
2011-10-24 16:47:00 -04:00
} else {
addReplyError ( c , " Unknown SCRIPT subcommand or wrong # of args. " ) ;
}
}