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
* generated by Redis directly . This allows use to go faster .
* 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 ) ;
}
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
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 ;
}
2011-09-27 09:30:31 -04:00
if ( cmd - > flags & REDIS_CMD_WRITE & & server . lua_random_dirty ) {
luaPushError ( lua ,
" Write commands not allowed after non deterministic commands " ) ;
goto cleanup ;
}
if ( cmd - > flags & REDIS_CMD_RANDOM ) server . lua_random_dirty = 1 ;
2011-09-27 07:57:10 -04:00
/* Run the command */
2011-04-30 16:29:21 -04:00
cmd - > proc ( c ) ;
/* 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 ) ;
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 ) {
redisLog ( REDIS_WARNING , " Lua slow script detected: still in execution after %lld milliseconds. You can shut down the server using the SHUTDOWN command. " , elapsed ) ;
server . lua_timedout = 1 ;
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-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 ) ;
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 ) ;
2011-10-19 10:42:10 -04:00
luaLoadLib ( lua , " cjson " , luaopen_cjson ) ;
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 " ) ;
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 ) ;
funcdef = sdscatlen ( funcdef , " () \n " , 4 ) ;
funcdef = sdscatlen ( funcdef , body - > ptr , sdslen ( body - > ptr ) ) ;
funcdef = sdscatlen ( funcdef , " \n end \n " , 5 ) ;
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-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 ) ;
server . lua_time_start = ustime ( ) / 1000 ;
} 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 . */
if ( lua_pcall ( lua , 0 , 1 , 0 ) ) {
2011-10-27 08:49:10 -04:00
server . lua_timedout = 0 ;
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-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-10-24 16:47:00 -04:00
} else {
addReplyError ( c , " Unknown SCRIPT subcommand or wrong # of args. " ) ;
}
}