blob: 56538a13cfe91d2c26808fac8876beff3fd971fd [file] [log] [blame]
// Copyright Redis(https://github.com/redis/redis)
// Copyright Kvrocks Core Team (bugfix and adapted to kvrocks coding style)
// All rights reserved.
//
#include "scripting.h"
#include <math.h>
#include <string>
#include "util.h"
#include "sha1.h"
#include "server.h"
#include "redis_connection.h"
#include "redis_cmd.h"
#include "rand.h"
/* The maximum number of characters needed to represent a long double
* as a string (long double has a huge range).
* This should be the size of the buffer given to doule to string */
#define MAX_LONG_DOUBLE_CHARS 5*1024
enum {
LL_DEBUG = 0,
LL_VERBOSE,
LL_NOTICE,
LL_WARNING,
};
extern "C" {
LUALIB_API int (luaopen_cjson)(lua_State *L);
LUALIB_API int (luaopen_struct)(lua_State *L);
LUALIB_API int (luaopen_cmsgpack)(lua_State *L);
LUALIB_API int (luaopen_bit)(lua_State *L);
}
namespace Lua {
lua_State* CreateState() {
lua_State *lua = lua_open();
loadLibraries(lua);
removeUnsupportedFunctions(lua);
loadFuncs(lua);
enableGlobalsProtection(lua);
return lua;
}
void DestroyState(lua_State *lua) {
lua_close(lua);
}
void loadFuncs(lua_State *lua) {
lua_newtable(lua);
/* redis.call */
lua_pushstring(lua, "call");
lua_pushcfunction(lua, redisCallCommand);
lua_settable(lua, -3);
/* redis.pcall */
lua_pushstring(lua, "pcall");
lua_pushcfunction(lua, redisPCallCommand);
lua_settable(lua, -3);
/* redis.log and log levels. */
lua_pushstring(lua, "log");
lua_pushcfunction(lua, redisLogCommand);
lua_settable(lua, -3);
lua_pushstring(lua, "LOG_DEBUG");
lua_pushnumber(lua, LL_DEBUG);
lua_settable(lua, -3);
lua_pushstring(lua, "LOG_VERBOSE");
lua_pushnumber(lua, LL_VERBOSE);
lua_settable(lua, -3);
lua_pushstring(lua, "LOG_NOTICE");
lua_pushnumber(lua, LL_NOTICE);
lua_settable(lua, -3);
lua_pushstring(lua, "LOG_WARNING");
lua_pushnumber(lua, LL_WARNING);
lua_settable(lua, -3);
/* redis.sha1hex */
lua_pushstring(lua, "sha1hex");
lua_pushcfunction(lua, redisSha1hexCommand);
lua_settable(lua, -3);
/* redis.error_reply and redis.status_reply */
lua_pushstring(lua, "error_reply");
lua_pushcfunction(lua, redisErrorReplyCommand);
lua_settable(lua, -3);
lua_pushstring(lua, "status_reply");
lua_pushcfunction(lua, redisStatusReplyCommand);
lua_settable(lua, -3);
lua_setglobal(lua, "redis");
/* Replace math.random and math.randomseed with our implementations. */
lua_getglobal(lua, "math");
lua_pushstring(lua, "random");
lua_pushcfunction(lua, redisMathRandom);
lua_settable(lua, -3);
lua_pushstring(lua, "randomseed");
lua_pushcfunction(lua, redisMathRandomSeed);
lua_settable(lua, -3);
lua_setglobal(lua, "math");
/* 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. */
{
const char *err_func = "local dbg = debug\n"
"function __redis__err__handler(err)\n"
" local i = dbg.getinfo(2,'nSl')\n"
" if i and i.what == 'C' then\n"
" i = dbg.getinfo(3,'nSl')\n"
" end\n"
" if i then\n"
" return i.source .. ':' .. i.currentline .. ': ' .. err\n"
" else\n"
" return err\n"
" end\n"
"end\n";
luaL_loadbuffer(lua, err_func, strlen(err_func), "@err_handler_def");
lua_pcall(lua, 0, 0, 0);
}
{
const 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);
}
}
int redisLogCommand(lua_State *lua) {
int j, level, argc = lua_gettop(lua);
if (argc < 2) {
lua_pushstring(lua, "redis.log() requires two arguments or more.");
return lua_error(lua);
}
if (!lua_isnumber(lua, -argc)) {
lua_pushstring(lua, "First argument must be a number (log level).");
return lua_error(lua);
}
level = lua_tonumber(lua, -argc);
if (level < LL_DEBUG || level > LL_WARNING) {
lua_pushstring(lua, "Invalid debug level.");
return lua_error(lua);
}
if (level < GetServer()->GetConfig()->loglevel) {
return 0;
}
std::string log_message;
for (j = 1; j < argc; j++) {
size_t len;
const char *s;
s = lua_tolstring(lua, (-argc)+j, &len);
if (s) {
if (j != 1) {
log_message += " "+std::string(s, len);
} else {
log_message = std::string(s, len);
}
}
}
// The min log level was INFO, DEBUG would never take effect
switch (level) {
case LL_VERBOSE: // also regard VERBOSE as INFO here since no VERBOSE level
case LL_NOTICE:
LOG(INFO) << "[Lua] " << log_message;
break;
case LL_WARNING:
LOG(WARNING) << "[Lua] " << log_message;
break;
}
return 0;
}
Status evalGenericCommand(Redis::Connection *conn,
const std::vector<std::string> &args,
bool evalsha,
std::string *output) {
int64_t numkeys = 0;
char funcname[43];
Server *srv = conn->GetServer();
lua_State *lua = srv->Lua();
auto s = Util::StringToNum(args[2], &numkeys);
if (!s.IsOK()) {
return s;
}
if (numkeys > int64_t(args.size()-3)) {
return Status(Status::NotOK, "Number of keys can't be greater than number of args");
} else if (numkeys < -1) {
return Status(Status::NotOK, "Number of keys can't be negative");
}
/* We obtain the script SHA1, then check if this function is already
* defined into the Lua state */
funcname[0] = 'f';
funcname[1] = '_';
if (!evalsha) {
SHA1Hex(funcname+2, args[1].c_str(), args[1].size());
} else {
for (int j = 0; j < 40; j++) {
std::string sha = args[1];
funcname[j+2] = (sha[j] >= 'A' && sha[j] <= 'Z') ? sha[j]+('a'-'A') : sha[j];
}
funcname[42] = '\0';
}
/* Push the pcall error handler function on the stack. */
lua_getglobal(lua, "__redis__err__handler");
/* Try to lookup the Lua function */
lua_getglobal(lua, funcname);
if (lua_isnil(lua, -1)) {
lua_pop(lua, 1); /* remove the nil from the stack */
std::string body;
if (evalsha) {
auto s = srv->ScriptGet(funcname+2, &body);
if (!s.IsOK()) {
lua_pop(lua, 1); /* remove the error handler from the stack. */
return Status(Status::NotOK, "NOSCRIPT No matching script. Please use EVAL");
}
} else {
body = args[1];
}
std::string sha;
s = createFunction(srv, body, &sha);
if (!s.IsOK()) {
lua_pop(lua, 1); /* remove the error handler from the stack. */
return s;
}
/* Now the following is guaranteed to return non nil */
lua_getglobal(lua, funcname);
}
/* Populate the argv and keys table accordingly to the arguments that
* EVAL received. */
setGlobalArray(lua, "KEYS", std::vector<std::string>(args.begin()+3, args.begin()+3+numkeys));
setGlobalArray(lua, "ARGV", std::vector<std::string>(args.begin()+3+numkeys, args.end()));
int err = lua_pcall(lua, 0, 1, -2);
if (err) {
std::string msg = std::string("ERR running script (call to ") + funcname + "): "+ lua_tostring(lua, -1);
*output = Redis::Error(msg);
lua_pop(lua, 2);
} else {
*output = replyToRedisReply(lua);
lua_pop(lua, 1);
}
/* 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 int64_t gc_count = 0;
gc_count++;
if (gc_count == LUA_GC_CYCLE_PERIOD) {
lua_gc(lua, LUA_GCSTEP, LUA_GC_CYCLE_PERIOD);
gc_count = 0;
}
}
return Status::OK();
}
int redisCallCommand(lua_State *lua) {
return redisGenericCommand(lua, 1);
}
int redisPCallCommand(lua_State *lua) {
return redisGenericCommand(lua, 0);
}
int redisGenericCommand(lua_State *lua, int raise_error) {
int j, argc = lua_gettop(lua);
std::vector<std::string> args;
if (argc == 0) {
pushError(lua, "Please specify at least one argument for redis.call()");
return raise_error ? raiseError(lua) : 1;
}
for (j = 0; j < argc; j++) {
char dbuf[64];
if (lua_type(lua, j+1) == LUA_TNUMBER) {
lua_Number num = lua_tonumber(lua, j+1);
snprintf(dbuf, sizeof(dbuf), "%.17g", static_cast<double>(num));
args.emplace_back(dbuf);
} else {
const char *obj_s;
size_t obj_len;
obj_s = lua_tolstring(lua, j+1, &obj_len);
if (obj_s == nullptr) break; /* no a string */
args.emplace_back(std::string(obj_s, obj_len));
}
}
if (j != argc) {
pushError(lua, "Lua redis() command arguments must be strings or integers");
return raise_error ? raiseError(lua) : 1;
}
auto commands = Redis::GetCommands();
auto cmd_iter = commands->find(Util::ToLower(args[0]));
if (cmd_iter == commands->end()) {
pushError(lua, "Unknown Redis command called from Lua script");
return raise_error ? raiseError(lua) : 1;
}
auto redisCmd = cmd_iter->second;
auto cmd = redisCmd->factory();
cmd->SetAttributes(redisCmd);
cmd->SetArgs(args);
int arity = cmd->GetAttributes()->arity;
if (((arity > 0 && argc != arity) || (arity < 0 && argc < -arity))) {
pushError(lua, "Wrong number of args calling Redis command From Lua script");
return raise_error ? raiseError(lua) : 1;
}
auto attributes = cmd->GetAttributes();
if (attributes->flags & Redis::kCmdNoScript) {
pushError(lua, "This Redis command is not allowed from scripts");
return raise_error ? raiseError(lua) : 1;
}
std::string output, cmd_name = Util::ToLower(args[0]);
Server *srv = GetServer();
Config *config = srv->GetConfig();
Redis::Connection *conn = srv->GetCurrentConnection();
if (config->cluster_enabled) {
auto s = srv->cluster_->CanExecByMySelf(attributes, args);
if (!s.IsOK()) {
pushError(lua, s.Msg().c_str());
return raise_error ? raiseError(lua) : 1;
}
}
if (config->slave_readonly && srv->IsSlave() && attributes->is_write()) {
pushError(lua, "READONLY You can't write against a read only slave.");
return raise_error ? raiseError(lua) : 1;
}
if (!config->slave_serve_stale_data && srv->IsSlave()
&& cmd_name != "info" && cmd_name != "slaveof"
&& srv->GetReplicationState() != kReplConnected) {
pushError(lua, "MASTERDOWN Link with MASTER is down "
"and slave-serve-stale-data is set to 'no'.");
return raise_error ? raiseError(lua) : 1;
}
auto s = cmd->Parse(args);
if (!s.IsOK()) {
pushError(lua, s.Msg().data());
return raise_error ? raiseError(lua) : 1;
}
srv->stats_.IncrCalls(cmd_name);
auto start = std::chrono::high_resolution_clock::now();
bool is_profiling = conn->isProfilingEnabled(cmd_name);
auto end = std::chrono::high_resolution_clock::now();
s = cmd->Execute(GetServer(), srv->GetCurrentConnection(), &output);
uint64_t duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
if (is_profiling) conn->recordProfilingSampleIfNeed(cmd_name, duration);
srv->SlowlogPushEntryIfNeeded(&args, duration);
srv->stats_.IncrLatency(static_cast<uint64_t>(duration), cmd_name);
srv->FeedMonitorConns(conn, args);
if (!s.IsOK()) {
pushError(lua, s.Msg().data());
return raise_error ? raiseError(lua) : 1;
}
redisProtocolToLuaType(lua, output.data());
return 1;
}
void removeUnsupportedFunctions(lua_State *lua) {
lua_pushnil(lua);
lua_setglobal(lua, "loadfile");
lua_pushnil(lua);
lua_setglobal(lua, "dofile");
}
void enableGlobalsProtection(lua_State *lua) {
const char *s[32];
int j = 0;
s[j++] = "local dbg=debug\n";
s[j++] = "local mt = {}\n";
s[j++] = "setmetatable(_G, mt)\n";
s[j++] = "mt.__newindex = function (t, n, v)\n";
s[j++] = " if dbg.getinfo(2) then\n";
s[j++] = " local w = dbg.getinfo(2, \"S\").what\n";
s[j++] = " if w ~= \"main\" and w ~= \"C\" then\n";
s[j++] = " error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n";
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";
s[j++] = " if dbg.getinfo(2) and dbg.getinfo(2, \"S\").what ~= \"C\" then\n";
s[j++] = " error(\"Script attempted to access nonexistent global variable '\"..tostring(n)..\"'\", 2)\n";
s[j++] = " end\n";
s[j++] = " return rawget(t, n)\n";
s[j++] = "end\n";
s[j++] = "debug = nil\n";
s[j++] = nullptr;
std::string code;
for (j = 0; s[j] != nullptr; j++) code += s[j];
luaL_loadbuffer(lua, code.c_str(), code.size(), "@enable_strict_lua");
lua_pcall(lua, 0, 0, 0);
}
void loadLibraries(lua_State *lua) {
auto loadLib = [] (lua_State *lua, const char *libname, lua_CFunction func) {
lua_pushcfunction(lua, func);
lua_pushstring(lua, libname);
lua_call(lua, 1, 0);
};
loadLib(lua, "", luaopen_base);
loadLib(lua, LUA_TABLIBNAME, luaopen_table);
loadLib(lua, LUA_STRLIBNAME, luaopen_string);
loadLib(lua, LUA_MATHLIBNAME, luaopen_math);
loadLib(lua, LUA_DBLIBNAME, luaopen_debug);
loadLib(lua, "cjson", luaopen_cjson);
loadLib(lua, "struct", luaopen_struct);
loadLib(lua, "cmsgpack", luaopen_cmsgpack);
loadLib(lua, "bit", luaopen_bit);
}
/* 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 redisReturnSingleFieldTable(lua_State *lua, const char *field) {
if (lua_gettop(lua) != 1 || lua_type(lua, -1) != LUA_TSTRING) {
pushError(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;
}
/* redis.error_reply() */
int redisErrorReplyCommand(lua_State *lua) {
return redisReturnSingleFieldTable(lua, "err");
}
/* redis.status_reply() */
int redisStatusReplyCommand(lua_State *lua) {
return redisReturnSingleFieldTable(lua, "ok");
}
/* This adds redis.sha1hex(string) to Lua scripts using the same hashing
* function used for sha1ing lua scripts. */
int redisSha1hexCommand(lua_State *lua) {
int argc = lua_gettop(lua);
char digest[41];
size_t len;
const char *s;
if (argc != 1) {
lua_pushstring(lua, "wrong number of arguments");
return lua_error(lua);
}
s = static_cast<const char *>(lua_tolstring(lua, 1, &len));
SHA1Hex(digest, s, len);
lua_pushstring(lua, digest);
return 1;
}
/* ---------------------------------------------------------------------------
* Utility functions.
* ------------------------------------------------------------------------- */
/* Perform the SHA1 of the input string. We use this both for hashing script
* bodies in order to obtain the Lua function name, and in the implementation
* of redis.sha1().
*
* 'digest' should point to a 41 bytes buffer: 40 for SHA1 converted into an
* hexadecimal number, plus 1 byte for null term. */
void SHA1Hex(char *digest, const char *script, size_t len) {
SHA1_CTX ctx;
unsigned char hash[20];
const char *cset = "0123456789abcdef";
int j;
SHA1Init(&ctx);
SHA1Update(&ctx, (const unsigned char*)script, len);
SHA1Final(hash, &ctx);
for (j = 0; j < 20; j++) {
digest[j*2] = cset[((hash[j]&0xF0)>>4)];
digest[j*2+1] = cset[(hash[j]&0xF)];
}
digest[40] = '\0';
}
/*
* ---------------------------------------------------------------------------
* Redis reply to Lua type conversion functions.
* ------------------------------------------------------------------------- */
/* 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 trivial 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. 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 us to go faster.
*
* Errors are returned as a table with a single 'err' field set to the
* error string.
*/
const char * redisProtocolToLuaType(lua_State *lua, const char *reply) {
const 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;
case '-': p = redisProtocolToLuaType_Error(lua, reply); break;
case '*': p = redisProtocolToLuaType_Aggregate(lua, reply, *p); break;
case '%': p = redisProtocolToLuaType_Aggregate(lua, reply, *p); break;
case '~': p = redisProtocolToLuaType_Aggregate(lua, reply, *p); break;
case '_': p = redisProtocolToLuaType_Null(lua, reply); break;
case '#': p = redisProtocolToLuaType_Bool(lua, reply, p[1]); break;
case ',': p = redisProtocolToLuaType_Double(lua, reply); break;
}
return p;
}
const char *redisProtocolToLuaType_Int(lua_State *lua, const char *reply) {
const char *p = strchr(reply+1, '\r');
int64_t value;
Util::StringToNum(std::string(reply+1, p-reply-1), &value);
lua_pushnumber(lua, static_cast<lua_Number>(value));
return p+2;
}
const char *redisProtocolToLuaType_Bulk(lua_State *lua, const char *reply) {
const char *p = strchr(reply+1, '\r');
int64_t bulklen;
Util::StringToNum(std::string(reply+1, p-reply-1), &bulklen);
if (bulklen == -1) {
lua_pushboolean(lua, 0);
return p+2;
} else {
lua_pushlstring(lua, p+2, bulklen);
return p+2+bulklen+2;
}
}
const char *redisProtocolToLuaType_Status(lua_State *lua, const char *reply) {
const char *p = strchr(reply+1, '\r');
lua_newtable(lua);
lua_pushstring(lua, "ok");
lua_pushlstring(lua, reply+1, p-reply-1);
lua_settable(lua, -3);
return p+2;
}
const char *redisProtocolToLuaType_Error(lua_State *lua, const char *reply) {
const 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;
}
const char *redisProtocolToLuaType_Aggregate(lua_State *lua, const char *reply, int atype) {
const char *p = strchr(reply+1, '\r');
int64_t mbulklen;
int j = 0;
Util::StringToNum(std::string(reply+1, p-reply-1), &mbulklen);
p += 2;
if (mbulklen == -1) {
lua_pushboolean(lua, 0);
return p;
}
lua_newtable(lua);
for (j = 0; j < mbulklen; j++) {
lua_pushnumber(lua, j+1);
p = redisProtocolToLuaType(lua, p);
lua_settable(lua, -3);
}
return p;
}
const char *redisProtocolToLuaType_Null(lua_State *lua, const char *reply) {
const char *p = strchr(reply+1, '\r');
lua_pushnil(lua);
return p+2;
}
const char *redisProtocolToLuaType_Bool(lua_State *lua, const char *reply, int tf) {
const char *p = strchr(reply+1, '\r');
lua_pushboolean(lua, tf == 't');
return p+2;
}
const char *redisProtocolToLuaType_Double(lua_State *lua, const char *reply) {
const char *p = strchr(reply+1, '\r');
char buf[MAX_LONG_DOUBLE_CHARS+1];
size_t len = p-reply-1;
double d;
if (len <= MAX_LONG_DOUBLE_CHARS) {
memcpy(buf, reply+1, len);
buf[len] = '\0';
d = strtod(buf, nullptr); /* We expect a valid representation. */
} else {
d = 0;
}
lua_newtable(lua);
lua_pushstring(lua, "double");
lua_pushnumber(lua, d);
lua_settable(lua, -3);
return p+2;
}
/* This function is used in order to push an error on the Lua stack in the
* format used by redis.pcall to return errors, which is a lua table
* with a single "err" field set to the error string. Note that this
* table is never a valid reply by proper commands, since the returned
* tables are otherwise always indexed by integers, never by strings. */
void pushError(lua_State *lua, const char *err) {
lua_newtable(lua);
lua_pushstring(lua, "err");
lua_pushstring(lua, err);
lua_settable(lua, -3);
}
std::string replyToRedisReply(lua_State *lua) {
std::string output;
const char *obj_s = nullptr;
size_t obj_len;
int t = lua_type(lua, -1);
switch (t) {
case LUA_TSTRING:
obj_s = lua_tolstring(lua, -1, &obj_len);
output = Redis::BulkString(std::string(obj_s, obj_len));
break;
case LUA_TBOOLEAN:
output = lua_toboolean(lua, -1) ? Redis::Integer(1) : Redis::NilString();
break;
case LUA_TNUMBER:
output = Redis::Integer((int64_t)(lua_tonumber(lua, -1)));
break;
case LUA_TTABLE:
/* We need to check if it is an array, an error, or a status reply.
* Error are returned as a single element table with 'err' field.
* Status replies are returned as single element table with 'ok'
* field. */
/* Handle error reply. */
lua_pushstring(lua, "err");
lua_gettable(lua, -2);
t = lua_type(lua, -1);
if (t == LUA_TSTRING) {
output = Redis::Error(lua_tostring(lua, -1));
lua_pop(lua, 2);
return output;
}
lua_pop(lua, 1); /* Discard field name pushed before. */
/* Handle status reply. */
lua_pushstring(lua, "ok");
lua_gettable(lua, -2);
t = lua_type(lua, -1);
if (t == LUA_TSTRING) {
obj_s = lua_tolstring(lua, -1, &obj_len);
output = Redis::BulkString(std::string(obj_s, obj_len));
lua_pop(lua, 1);
return output;
} else {
int j = 1, mbulklen = 0;
lua_pop(lua, 1); /* Discard the 'ok' field value we popped */
while (true) {
lua_pushnumber(lua, j++);
lua_gettable(lua, -2);
t = lua_type(lua, -1);
if (t == LUA_TNIL) {
lua_pop(lua, 1);
break;
}
mbulklen++;
output += replyToRedisReply(lua);
}
output = Redis::MultiLen(mbulklen)+output;
}
break;
default:
output = Redis::NilString();
}
lua_pop(lua, 1);
return output;
}
/* In case the error set into the Lua stack by pushError() was generated
* by the non-error-trapping version of redis.pcall(), which is redis.call(),
* this function will raise the Lua error so that the execution of the
* script will be halted. */
int raiseError(lua_State *lua) {
lua_pushstring(lua, "err");
lua_gettable(lua, -2);
return lua_error(lua);
}
/* 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 sortArray(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 */
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 */
lua_pop(lua, 1); /* Stack: array (sorted) */
}
void setGlobalArray(lua_State *lua, const std::string &var, const std::vector<std::string> &elems) {
lua_newtable(lua);
for (size_t i = 0; i < elems.size(); i++) {
lua_pushlstring(lua, elems[i].c_str(), elems[i].size());
lua_rawseti(lua, -2, i+1);
}
lua_setglobal(lua, var.c_str());
}
/* ---------------------------------------------------------------------------
* Redis provided math.random
* ------------------------------------------------------------------------- */
/* We replace math.random() with our implementation that is not affected
* by specific libc random() implementations and will output the same sequence
* (for the same seed) in every arch. */
/* The following implementation is the one shipped with Lua itself but with
* rand() replaced by redisLrand48(). */
int redisMathRandom(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 redisMathRandomSeed(lua_State *L) {
redisSrand48(luaL_checkint(L, 1));
return 0;
}
/* ---------------------------------------------------------------------------
* EVAL and SCRIPT commands implementation
* ------------------------------------------------------------------------- */
/* Define a Lua function with the specified body.
* The function name will be generated in the following form:
*
* f_<hex sha1 sum>
*
* The function increments the reference count of the 'body' object as a
* side effect of a successful call.
*
* On success a pointer to an SDS string representing the function SHA1 of the
* just added function is returned (and will be valid until the next call
* to scriptingReset() function), otherwise NULL is returned.
*
* The function handles the fact of being called with a script that already
* exists, and in such a case, it behaves like in the success case.
*
* If 'c' is not NULL, on error the client is informed with an appropriate
* error describing the nature of the problem and the Lua interpreter error. */
Status createFunction(Server *srv, const std::string &body, std::string *sha) {
char funcname[43];
funcname[0] = 'f';
funcname[1] = '_';
SHA1Hex(funcname+2, body.c_str(), body.size());
*sha = funcname+2;
std::string funcdef;
funcdef += "function ";
funcdef += funcname;
funcdef += "() ";
funcdef += body;
funcdef += "\nend";
lua_State *lua = srv->Lua();
if (luaL_loadbuffer(lua, funcdef.c_str(), funcdef.size(), "@user_script")) {
std::string errMsg = lua_tostring(lua, -1);
lua_pop(lua, 1);
return Status(Status::NotOK, "Error compiling script (new function): " + errMsg +"\n");
}
if (lua_pcall(lua, 0, 0, 0)) {
std::string errMsg = lua_tostring(lua, -1);
lua_pop(lua, 1);
return Status(Status::NotOK,
"Error running script (new function): " + errMsg + "\n");
}
// would store lua function into propagate column family and propagate those scripts to slaves
srv->ScriptSet(*sha, body);
return Status::OK();
}
} // namespace Lua