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 .
*/
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 ) ;
2012-03-28 14:10:24 -04:00
void sha1hex ( char * digest , char * script , size_t len ) ;
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
* 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 .
2013-01-16 12:00:20 -05:00
* The reply string can be altered during the parsing as it is discarded
2011-05-01 08:47:52 -04:00
* 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 ) {
2013-05-22 19:17:58 -04:00
lua_Debug dbg ;
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 ) ;
}
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) */
}
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 ;
redisClient * c = server . lua_client ;
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 ] ;
static int cached_objects_len [ LUA_CMD_OBJCACHE_SIZE ] ;
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() " ) ;
return 1 ;
}
2011-05-01 08:47:52 -04:00
/* Build the arguments vector */
2014-05-06 05:13:00 -04:00
if ( ! argv ) {
argv = zmalloc ( sizeof ( robj * ) * argc ) ;
} else if ( argv_size < argc ) {
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 ;
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. */
if ( cached_objects [ j ] & & cached_objects_len [ j ] > = obj_len ) {
char * s = cached_objects [ j ] - > ptr ;
struct sdshdr * sh = ( void * ) ( s - ( sizeof ( struct sdshdr ) ) ) ;
argv [ j ] = cached_objects [ j ] ;
cached_objects [ j ] = NULL ;
memcpy ( s , obj_s , obj_len + 1 ) ;
sh - > free + = sh - > len - obj_len ;
sh - > len = obj_len ;
} else {
argv [ j ] = createStringObject ( obj_s , obj_len ) ;
}
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 " ) ;
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 & &
2013-06-19 12:25:03 -04:00
! server . loading & &
2012-03-20 12:32:48 -04:00
! ( 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 . */
2014-05-05 16:14:23 -04:00
if ( listLength ( c - > reply ) = = 0 & & c - > bufpos < REDIS_REPLY_CHUNK_BYTES ) {
/* 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 ) ) {
robj * o = listNodeValue ( listFirst ( c - > reply ) ) ;
2011-04-30 16:29:21 -04:00
2014-05-05 16:14:23 -04:00
reply = sdscatlen ( reply , o - > ptr , sdslen ( o - > ptr ) ) ;
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 ) ;
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
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 & &
( o - > encoding = = REDIS_ENCODING_RAW | |
o - > encoding = = REDIS_ENCODING_EMBSTR ) & &
sdslen ( o - > ptr ) < = LUA_CMD_OBJCACHE_MAX_LEN )
{
struct sdshdr * sh = ( void * ) ( ( ( char * ) ( o - > ptr ) ) - ( sizeof ( struct sdshdr ) ) ) ;
if ( cached_objects [ j ] ) decrRefCount ( cached_objects [ j ] ) ;
cached_objects [ j ] = o ;
cached_objects_len [ j ] = sh - > free + sh - > len ;
} else {
decrRefCount ( o ) ;
}
}
2014-05-06 05:13:00 -04:00
if ( c - > argv ! = argv ) {
zfree ( c - > argv ) ;
argv = NULL ;
}
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 . */
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 ) ;
}
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 ) {
luaPushError ( lua , " wrong number of arguments " ) ;
return 1 ;
}
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 ;
}
int luaRedisErrorReplyCommand ( lua_State * lua ) {
return luaRedisReturnSingleFieldTable ( lua , " err " ) ;
}
int luaRedisStatusReplyCommand ( lua_State * lua ) {
return luaRedisReturnSingleFieldTable ( lua , " ok " ) ;
}
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
2014-02-03 09:45:40 -05:00
elapsed = mstime ( ) - 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
}
2014-04-24 11:36:47 -04:00
if ( server . lua_timedout ) processEventsWhileBlocked ( ) ;
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
}
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 . */
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 " ;
2012-04-13 07:36:08 -04:00
s [ j + + ] = " if debug.getinfo(2) then \n " ;
2012-04-13 05:16:50 -04:00
s [ j + + ] = " local w = debug.getinfo(2, \" S \" ).what \n " ;
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 " ;
2012-04-13 07:36:08 -04:00
s [ j + + ] = " if debug.getinfo(2) and debug.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 " ;
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.
* 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 ( ) ;
2012-04-23 04:43:24 -04: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 ) ;
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 ) ;
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 ) ;
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 . */
{
char * errh_func = " function __redis__err__handler(err) \n "
" local i = debug.getinfo(2,'nSl') \n "
" if i and i.what == 'C' then \n "
" i = debug.getinfo(3,'nSl') \n "
" 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 ) ;
server . lua_client - > flags | = REDIS_LUA_CLIENT ;
}
2011-04-30 16:29:21 -04:00
2012-03-29 06:02:28 -04:00
/* Lua beginners ofter don't use "local", this is likely to introduce
* subtle bugs in their code . To prevent problems we protect accesses
* to global variables . */
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 ( ) ;
scriptingInit ( ) ;
}
2013-01-16 12:00:20 -05:00
/* Perform the SHA1 of the input string. We use this both for hashing script
2012-03-28 14:10:24 -04:00
* 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
2011-04-30 11:46:52 -04:00
* hexadecimal number , plus 1 byte for null term . */
2012-03-28 14:10:24 -04:00
void sha1hex ( char * digest , char * script , size_t len ) {
2011-04-30 11:46:52 -04:00
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 :
2012-10-01 04:10:03 -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 .
2013-01-16 12:00:20 -05:00
* Status replies are returned as single element 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 ;
}
2012-09-28 08:19:15 -04:00
2012-10-01 04:10:03 -04:00
lua_pop ( lua , 1 ) ;
2011-05-02 04:08:26 -04:00
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
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 ) ;
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 ;
2013-01-10 04:46:05 -05:00
int delhook = 0 , err ;
2011-05-01 07:07:44 -04:00
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 */
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 ;
}
2013-08-29 05:49:23 -04:00
if ( luaCreateFunction ( c , lua , funcname , c - > argv [ 1 ] ) = = REDIS_ERR ) {
lua_pop ( lua , 1 ) ; /* remove the error handler from the stack. */
/* The error is sent to the client by luaCreateFunction()
* itself when it returns REDIS_ERR . */
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 ) ;
2013-06-18 11:33:35 -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
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
* make the Lua script execution slower . */
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 ;
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 ) ;
2012-04-27 05:41:25 -04:00
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. */
2012-04-27 05:41:25 -04:00
if ( delhook ) lua_sethook ( lua , luaMaskCountHook , 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 ;
2011-05-02 18:07:41 -04:00
selectDb ( c , server . lua_client - > db - > id ) ; /* set DB ID from Lua client */
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
}
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 . */
2011-07-13 09:38:03 -04:00
if ( evalsha ) {
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 ) ;
redisAssertWithInfo ( c , NULL , script ! = NULL ) ;
rewriteClientCommandArgument ( c , 0 ,
resetRefCount ( createStringObject ( " EVAL " , 4 ) ) ) ;
rewriteClientCommandArgument ( c , 1 , script ) ;
2014-02-13 06:10:43 -05:00
forceCommandPropagation ( c , REDIS_PROPAGATE_REPL | REDIS_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
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 ) ;
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 ] )
= = 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 ) ;
2013-06-25 06:49:56 -04:00
forceCommandPropagation ( c , REDIS_PROPAGATE_REPL | REDIS_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 ) ;
}
2011-10-24 16:47:00 -04:00
} else {
addReplyError ( c , " Unknown SCRIPT subcommand or wrong # of args. " ) ;
}
}