diff options
Diffstat (limited to 'src/cls/lua')
-rw-r--r-- | src/cls/lua/cls_lua.cc | 1052 | ||||
-rw-r--r-- | src/cls/lua/cls_lua.h | 14 | ||||
-rw-r--r-- | src/cls/lua/cls_lua_client.cc | 34 | ||||
-rw-r--r-- | src/cls/lua/cls_lua_client.h | 13 | ||||
-rw-r--r-- | src/cls/lua/cls_lua_ops.h | 31 | ||||
-rw-r--r-- | src/cls/lua/lua_bufferlist.cc | 180 |
6 files changed, 1324 insertions, 0 deletions
diff --git a/src/cls/lua/cls_lua.cc b/src/cls/lua/cls_lua.cc new file mode 100644 index 00000000..ecc5417f --- /dev/null +++ b/src/cls/lua/cls_lua.cc @@ -0,0 +1,1052 @@ +/* + * Lua Bindings for RADOS Object Class + */ +#include <errno.h> +#include <setjmp.h> +#include <string> +#include <sstream> +#include <lua.hpp> +#include "include/types.h" +#include "objclass/objclass.h" +#include "json_spirit/json_spirit.h" +#include "cls_lua.h" +#include "cls_lua_ops.h" + +CLS_VER(1,0) +CLS_NAME(lua) + +/* + * Jump point for recovering from Lua panic. + */ +static jmp_buf cls_lua_panic_jump; + +/* + * Handle Lua panic. + */ +static int cls_lua_atpanic(lua_State *lua) +{ + CLS_ERR("error: Lua panic: %s", lua_tostring(lua, -1)); + longjmp(cls_lua_panic_jump, 1); + return 0; +} + +struct clslua_err { + bool error; + int ret; +}; + +/* + * Input parameter encoding. + */ +enum InputEncoding { + JSON_ENC, + BUFFERLIST_ENC, +}; + +struct clslua_hctx { + struct clslua_err error; + InputEncoding in_enc; + int ret; + + cls_method_context_t *hctx; + bufferlist *inbl; // raw cls input + bufferlist *outbl; // raw cls output + + string script; // lua script + string handler; // lua handler + bufferlist input; // lua handler input +}; + +/* Lua registry key for method context */ +static char clslua_hctx_reg_key; + +/* + * Grabs the full method handler context + */ +static clslua_hctx *__clslua_get_hctx(lua_State *L) +{ + /* lookup registry value */ + lua_pushlightuserdata(L, &clslua_hctx_reg_key); + lua_gettable(L, LUA_REGISTRYINDEX); + + /* check cls_lua assumptions */ + ceph_assert(!lua_isnil(L, -1)); + ceph_assert(lua_type(L, -1) == LUA_TLIGHTUSERDATA); + + /* cast and cleanup stack */ + clslua_hctx *hctx = (struct clslua_hctx *)lua_touserdata(L, -1); + lua_pop(L, 1); + + return hctx; +} + +/* + * Get the method context out of the registry. This is called at the beginning + * of each clx_cxx_* wrapper, and must be set before there is any chance a Lua + * script calling a 'cls' module function that requires it. + */ +static cls_method_context_t clslua_get_hctx(lua_State *L) +{ + struct clslua_hctx *hctx = __clslua_get_hctx(L); + return *hctx->hctx; +} + +/* + * Returns a reference to cls_lua error state from registry. + */ +struct clslua_err *clslua_checkerr(lua_State *L) +{ + struct clslua_hctx *hctx = __clslua_get_hctx(L); + struct clslua_err *err = &hctx->error; + return err; +} + + +/* Registry key for real `pcall` function */ +static char clslua_pcall_reg_key; + +/* + * Wrap Lua pcall to check for errors thrown by cls_lua (e.g. I/O errors or + * bufferlist decoding errors). The global error is cleared before returning + * to the caller. + */ +static int clslua_pcall(lua_State *L) +{ + int nargs = lua_gettop(L); + lua_pushlightuserdata(L, &clslua_pcall_reg_key); + lua_gettable(L, LUA_REGISTRYINDEX); + lua_insert(L, 1); + lua_call(L, nargs, LUA_MULTRET); + struct clslua_err *err = clslua_checkerr(L); + ceph_assert(err); + if (err->error) { + err->error = false; + lua_pushinteger(L, err->ret); + lua_insert(L, -2); + } + return lua_gettop(L); +} + + +/* + * cls_log + */ +static int clslua_log(lua_State *L) +{ + int nargs = lua_gettop(L); + + if (!nargs) + return 0; + + int loglevel = LOG_LEVEL_DEFAULT; + bool custom_ll = false; + + /* check if first arg can be a log level */ + if (nargs > 1 && lua_isnumber(L, 1)) { + int ll = (int)lua_tonumber(L, 1); + if (ll >= 0) { + loglevel = ll; + custom_ll = true; + } + } + + /* check space for args and seperators (" ") */ + int nelems = ((nargs - (custom_ll ? 1 : 0)) * 2) - 1; + luaL_checkstack(L, nelems, "rados.log(..)"); + + for (int i = custom_ll ? 2 : 1; i <= nargs; i++) { + const char *part = lua_tostring(L, i); + if (!part) { + if (lua_type(L, i) == LUA_TBOOLEAN) + part = lua_toboolean(L, i) ? "true" : "false"; + else + part = luaL_typename(L, i); + } + lua_pushstring(L, part); + if ((i+1) <= nargs) + lua_pushstring(L, " "); + } + + /* join string parts and send to Ceph/reply log */ + lua_concat(L, nelems); + CLS_LOG(loglevel, "%s", lua_tostring(L, -1)); + + /* concat leaves result at top of stack */ + return 1; +} + +static char clslua_registered_handle_reg_key; + +/* + * Register a function to be used as a handler target + */ +static int clslua_register(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TFUNCTION); + + /* get table of registered handlers */ + lua_pushlightuserdata(L, &clslua_registered_handle_reg_key); + lua_gettable(L, LUA_REGISTRYINDEX); + ceph_assert(lua_type(L, -1) == LUA_TTABLE); + + /* lookup function argument */ + lua_pushvalue(L, 1); + lua_gettable(L, -2); + + if (lua_isnil(L, -1)) { + lua_pushvalue(L, 1); + lua_pushvalue(L, 1); + lua_settable(L, -4); + } else { + lua_pushstring(L, "Cannot register handler more than once"); + return lua_error(L); + } + + return 0; +} + +/* + * Check if a function is registered as a handler + */ +static void clslua_check_registered_handler(lua_State *L) +{ + luaL_checktype(L, -1, LUA_TFUNCTION); + + /* get table of registered handlers */ + lua_pushlightuserdata(L, &clslua_registered_handle_reg_key); + lua_gettable(L, LUA_REGISTRYINDEX); + ceph_assert(lua_type(L, -1) == LUA_TTABLE); + + /* lookup function argument */ + lua_pushvalue(L, -2); + lua_gettable(L, -2); + + if (!lua_rawequal(L, -1, -3)) { + lua_pushstring(L, "Handler is not registered"); + lua_error(L); + } + + lua_pop(L, 2); +} + +/* + * Handle result of a cls_cxx_* call. If @ok is non-zero then we return with + * the number of Lua return arguments on the stack. Otherwise we save error + * information in the registry and throw a Lua error. + */ +static int clslua_opresult(lua_State *L, int ok, int ret, int nargs, + bool error_on_stack = false) +{ + struct clslua_err *err = clslua_checkerr(L); + + ceph_assert(err); + if (err->error) { + CLS_ERR("error: cls_lua state machine: unexpected error"); + ceph_abort(); + } + + /* everything is cherry */ + if (ok) + return nargs; + + /* set error in registry */ + err->error = true; + err->ret = ret; + + /* push error message */ + if (!error_on_stack) + lua_pushfstring(L, "%s", strerror(-ret)); + + return lua_error(L); +} + +/* + * cls_cxx_create + */ +static int clslua_create(lua_State *lua) +{ + cls_method_context_t hctx = clslua_get_hctx(lua); + int exclusive = lua_toboolean(lua, 1); + + int ret = cls_cxx_create(hctx, exclusive); + return clslua_opresult(lua, (ret == 0), ret, 0); +} + +/* + * cls_cxx_remove + */ +static int clslua_remove(lua_State *lua) +{ + cls_method_context_t hctx = clslua_get_hctx(lua); + + int ret = cls_cxx_remove(hctx); + return clslua_opresult(lua, (ret == 0), ret, 0); +} + +/* + * cls_cxx_stat + */ +static int clslua_stat(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + + uint64_t size; + time_t mtime; + int ret = cls_cxx_stat(hctx, &size, &mtime); + if (!ret) { + lua_pushinteger(L, size); + lua_pushinteger(L, mtime); + } + return clslua_opresult(L, (ret == 0), ret, 2); +} + +/* + * cls_cxx_read + */ +static int clslua_read(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + int offset = luaL_checkinteger(L, 1); + int length = luaL_checkinteger(L, 2); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + int ret = cls_cxx_read(hctx, offset, length, bl); + return clslua_opresult(L, (ret >= 0), ret, 1); +} + +/* + * cls_cxx_write + */ +static int clslua_write(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + int offset = luaL_checkinteger(L, 1); + int length = luaL_checkinteger(L, 2); + bufferlist *bl = clslua_checkbufferlist(L, 3); + int ret = cls_cxx_write(hctx, offset, length, bl); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_write_full + */ +static int clslua_write_full(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + bufferlist *bl = clslua_checkbufferlist(L, 1); + int ret = cls_cxx_write_full(hctx, bl); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_getxattr + */ +static int clslua_getxattr(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *name = luaL_checkstring(L, 1); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + int ret = cls_cxx_getxattr(hctx, name, bl); + return clslua_opresult(L, (ret >= 0), ret, 1); +} + +/* + * cls_cxx_getxattrs + */ +static int clslua_getxattrs(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + + map<string, bufferlist> attrs; + int ret = cls_cxx_getxattrs(hctx, &attrs); + if (ret < 0) + return clslua_opresult(L, 0, ret, 0); + + lua_createtable(L, 0, attrs.size()); + + for (auto it = attrs.cbegin(); it != attrs.cend(); it++) { + lua_pushstring(L, it->first.c_str()); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + *bl = it->second; // xfer ownership... will be GC'd + lua_settable(L, -3); + } + + return clslua_opresult(L, 1, ret, 1); +} + +/* + * cls_cxx_setxattr + */ +static int clslua_setxattr(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *name = luaL_checkstring(L, 1); + bufferlist *bl = clslua_checkbufferlist(L, 2); + int ret = cls_cxx_setxattr(hctx, name, bl); + return clslua_opresult(L, (ret == 0), ret, 1); +} + +/* + * cls_cxx_map_get_val + */ +static int clslua_map_get_val(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *key = luaL_checkstring(L, 1); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + int ret = cls_cxx_map_get_val(hctx, key, bl); + return clslua_opresult(L, (ret == 0), ret, 1); +} + +/* + * cls_cxx_map_set_val + */ +static int clslua_map_set_val(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *key = luaL_checkstring(L, 1); + bufferlist *val = clslua_checkbufferlist(L, 2); + int ret = cls_cxx_map_set_val(hctx, key, val); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_map_clear + */ +static int clslua_map_clear(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + int ret = cls_cxx_map_clear(hctx); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_map_get_keys + */ +static int clslua_map_get_keys(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *start_after = luaL_checkstring(L, 1); + int max_to_get = luaL_checkinteger(L, 2); + + std::set<string> keys; + bool more; + int ret = cls_cxx_map_get_keys(hctx, start_after, max_to_get, &keys, &more); + if (ret < 0) + return clslua_opresult(L, 0, ret, 0); + + lua_createtable(L, 0, keys.size()); + + for (auto it = keys.cbegin(); it != keys.cend(); it++) { + const std::string& key = *it; + lua_pushstring(L, key.c_str()); + lua_pushboolean(L, 1); + lua_settable(L, -3); + } + + return clslua_opresult(L, 1, ret, 1); +} + +/* + * cls_cxx_map_get_vals + */ +static int clslua_map_get_vals(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *start_after = luaL_checkstring(L, 1); + const char *filter_prefix= luaL_checkstring(L, 2); + int max_to_get = luaL_checkinteger(L, 3); + + map<string, bufferlist> kvpairs; + bool more; + int ret = cls_cxx_map_get_vals(hctx, start_after, filter_prefix, + max_to_get, &kvpairs, &more); + if (ret < 0) + return clslua_opresult(L, 0, ret, 0); + + lua_createtable(L, 0, kvpairs.size()); + + for (auto it = kvpairs.cbegin(); it != kvpairs.cend(); it++) { + lua_pushstring(L, it->first.c_str()); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + *bl = it->second; // xfer ownership... will be GC'd + lua_settable(L, -3); + } + + return clslua_opresult(L, 1, ret, 1); +} + +/* + * cls_cxx_map_read_header + */ +static int clslua_map_read_header(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + bufferlist *bl = clslua_pushbufferlist(L, NULL); + int ret = cls_cxx_map_read_header(hctx, bl); + return clslua_opresult(L, (ret >= 0), ret, 1); +} + +/* + * cls_cxx_map_write_header + */ +static int clslua_map_write_header(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + bufferlist *bl = clslua_checkbufferlist(L, 1); + int ret = cls_cxx_map_write_header(hctx, bl); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_map_set_vals + */ +static int clslua_map_set_vals(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + luaL_checktype(L, 1, LUA_TTABLE); + + map<string, bufferlist> kvpairs; + + for (lua_pushnil(L); lua_next(L, 1); lua_pop(L, 1)) { + /* + * In the case of a numeric key a copy is made on the stack because + * converting to a string would otherwise manipulate the original key and + * cause problems for iteration. + */ + string key; + int type_code = lua_type(L, -2); + switch (type_code) { + case LUA_TSTRING: + key.assign(lua_tolstring(L, -2, NULL)); + break; + + case LUA_TNUMBER: + lua_pushvalue(L, -2); + key.assign(lua_tolstring(L, -1, NULL)); + lua_pop(L, 1); + break; + + default: + lua_pushfstring(L, "map_set_vals: invalid key type (%s)", + lua_typename(L, type_code)); + return clslua_opresult(L, 0, -EINVAL, 0, true); + } + + bufferlist val; + type_code = lua_type(L, -1); + switch (type_code) { + case LUA_TSTRING: + { + size_t len; + const char *data = lua_tolstring(L, -1, &len); + val.append(data, len); + } + break; + + default: + lua_pushfstring(L, "map_set_vals: invalid val type (%s) for key (%s)", + lua_typename(L, type_code), key.c_str()); + return clslua_opresult(L, 0, -EINVAL, 0, true); + } + + kvpairs[key] = val; + } + + int ret = cls_cxx_map_set_vals(hctx, &kvpairs); + + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_cxx_map_remove_key + */ +static int clslua_map_remove_key(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + const char *key = luaL_checkstring(L, 1); + int ret = cls_cxx_map_remove_key(hctx, key); + return clslua_opresult(L, (ret == 0), ret, 0); +} + +/* + * cls_current_version + */ +static int clslua_current_version(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + uint64_t version = cls_current_version(hctx); + lua_pushinteger(L, version); + return clslua_opresult(L, 1, 0, 1); +} + +/* + * cls_current_subop_num + */ +static int clslua_current_subop_num(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + int num = cls_current_subop_num(hctx); + lua_pushinteger(L, num); + return clslua_opresult(L, 1, 0, 1); +} + +/* + * cls_current_subop_version + */ +static int clslua_current_subop_version(lua_State *L) +{ + cls_method_context_t hctx = clslua_get_hctx(L); + string s; + cls_cxx_subop_version(hctx, &s); + lua_pushstring(L, s.c_str()); + return clslua_opresult(L, 1, 0, 1); +} + +/* + * Functions registered in the 'cls' module. + */ +static const luaL_Reg clslua_lib[] = { + // mgmt + {"register", clslua_register}, + {"log", clslua_log}, + + // data + {"create", clslua_create}, + {"remove", clslua_remove}, + {"stat", clslua_stat}, + {"read", clslua_read}, + {"write", clslua_write}, + {"write_full", clslua_write_full}, + + // xattr + {"getxattr", clslua_getxattr}, + {"getxattrs", clslua_getxattrs}, + {"setxattr", clslua_setxattr}, + + // omap + {"map_clear", clslua_map_clear}, + {"map_get_keys", clslua_map_get_keys}, + {"map_get_vals", clslua_map_get_vals}, + {"map_read_header", clslua_map_read_header}, + {"map_write_header", clslua_map_write_header}, + {"map_get_val", clslua_map_get_val}, + {"map_set_val", clslua_map_set_val}, + {"map_set_vals", clslua_map_set_vals}, + {"map_remove_key", clslua_map_remove_key}, + + // env + {"current_version", clslua_current_version}, + {"current_subop_num", clslua_current_subop_num}, + {"current_subop_version", clslua_current_subop_version}, + + {NULL, NULL} +}; + +/* + * Set const int in table at top of stack + */ +#define SET_INT_CONST(var) do { \ + lua_pushinteger(L, var); \ + lua_setfield(L, -2, #var); \ +} while (0) + +/* + * + */ +static int luaopen_objclass(lua_State *L) +{ + lua_newtable(L); + + /* + * Register cls functions (cls.log, etc...) + */ + luaL_setfuncs(L, clslua_lib, 0); + + /* + * Register generic errno values under 'cls' + */ + SET_INT_CONST(EPERM); + SET_INT_CONST(ENOENT); + SET_INT_CONST(ESRCH); + SET_INT_CONST(EINTR); + SET_INT_CONST(EIO); + SET_INT_CONST(ENXIO); + SET_INT_CONST(E2BIG); + SET_INT_CONST(ENOEXEC); + SET_INT_CONST(EBADF); + SET_INT_CONST(ECHILD); + SET_INT_CONST(EAGAIN); + SET_INT_CONST(ENOMEM); + SET_INT_CONST(EACCES); + SET_INT_CONST(EFAULT); + SET_INT_CONST(EBUSY); + SET_INT_CONST(EEXIST); + SET_INT_CONST(EXDEV); + SET_INT_CONST(ENODEV); + SET_INT_CONST(ENOTDIR); + SET_INT_CONST(EISDIR); + SET_INT_CONST(EINVAL); + SET_INT_CONST(ENFILE); + SET_INT_CONST(EMFILE); + SET_INT_CONST(ENOTTY); + SET_INT_CONST(EFBIG); + SET_INT_CONST(ENOSPC); + SET_INT_CONST(ESPIPE); + SET_INT_CONST(EROFS); + SET_INT_CONST(EMLINK); + SET_INT_CONST(EPIPE); + SET_INT_CONST(EDOM); + SET_INT_CONST(ERANGE); + + return 1; +} + +/* + * Setup the execution environment. Our sandbox currently is not + * sophisticated. With a new Lua state per-request we don't need to work about + * users stepping on each other, but we do rip out access to the local file + * system. All this will change when/if we decide to use some shared Lua + * states, most likely for performance reasons. + */ +static void clslua_setup_env(lua_State *L) +{ + luaL_requiref(L, "_G", luaopen_base, 1); + lua_pop(L, 1); + + /* + * Wrap `pcall` to intercept errors. First save a reference to the default + * Lua `pcall` function, and then replace `pcall` with our version. + */ + lua_pushlightuserdata(L, &clslua_pcall_reg_key); + lua_getglobal(L, "pcall"); + lua_settable(L, LUA_REGISTRYINDEX); + + lua_pushcfunction(L, clslua_pcall); + lua_setglobal(L, "pcall"); + + /* mask unsafe */ + lua_pushnil(L); + lua_setglobal(L, "loadfile"); + + /* mask unsafe */ + lua_pushnil(L); + lua_setglobal(L, "dofile"); + + /* not integrated into our error handling */ + lua_pushnil(L); + lua_setglobal(L, "xpcall"); + + luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1); + lua_pop(L, 1); + + luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1); + lua_pop(L, 1); + + luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1); + lua_pop(L, 1); + + luaL_requiref(L, "objclass", luaopen_objclass, 1); + lua_pop(L, 1); + + luaL_requiref(L, "bufferlist", luaopen_bufferlist, 1); + lua_pop(L, 1); +} + +/* + * Schema: + * { + * "script": "...", + * "handler": "...", + * "input": "..." # optional + * } + */ +static int unpack_json_command(lua_State *L, struct clslua_hctx *ctx, + std::string& script, std::string& handler, std::string& input, + size_t *input_len) +{ + std::string json_input(ctx->inbl->c_str()); + json_spirit::mValue value; + + if (!json_spirit::read(json_input, value)) { + CLS_ERR("error: unparseable JSON"); + ctx->ret = -EINVAL; + return 1; + } + + if (value.type() != json_spirit::obj_type) { + CLS_ERR("error: input not a JSON object"); + ctx->ret = -EINVAL; + return 1; + } + json_spirit::mObject obj = value.get_obj(); + + // grab the script + std::map<std::string, json_spirit::mValue>::const_iterator it = obj.find("script"); + if (it == obj.end()) { + CLS_ERR("error: 'script' field found in JSON object"); + ctx->ret = -EINVAL; + return 1; + } + + if (it->second.type() != json_spirit::str_type) { + CLS_ERR("error: script is not a string"); + ctx->ret = -EINVAL; + return 1; + } + script = it->second.get_str(); + + // grab the target function/handler name + it = obj.find("handler"); + if (it == obj.end()) { + CLS_ERR("error: no target handler found in JSON object"); + ctx->ret = -EINVAL; + return 1; + } + + if (it->second.type() != json_spirit::str_type) { + CLS_ERR("error: target handler is not a string"); + ctx->ret = -EINVAL; + return 1; + } + handler = it->second.get_str(); + + // grab the input (optional) + it = obj.find("input"); + if (it != obj.end()) { + if (it->second.type() != json_spirit::str_type) { + CLS_ERR("error: handler input is not a string"); + ctx->ret = -EINVAL; + return 1; + } + input = it->second.get_str(); + *input_len = input.size(); + } + + return 0; +} + +/* + * Runs the script, and calls handler. + */ +static int clslua_eval(lua_State *L) +{ + struct clslua_hctx *ctx = __clslua_get_hctx(L); + ctx->ret = -EIO; /* assume failure */ + + /* + * Load modules, errno value constants, and other environment goodies. Must + * be done before loading/compiling the chunk. + */ + clslua_setup_env(L); + + /* + * Deserialize the input that contains the script, the name of the handler + * to call, and the handler input. + */ + switch (ctx->in_enc) { + case JSON_ENC: + { + std::string input_str; + size_t input_str_len = 0; + + // if there is an error decoding json then ctx->ret will be set and we + // return normally from this function. + if (unpack_json_command(L, ctx, ctx->script, ctx->handler, input_str, + &input_str_len)) + return 0; + + bufferptr bp(input_str.c_str(), input_str_len); + ctx->input.push_back(bp); + } + break; + + case BUFFERLIST_ENC: + { + cls_lua_eval_op op; + + try { + auto it = ctx->inbl->cbegin(); + decode(op, it); + } catch (const buffer::error &err) { + CLS_ERR("error: could not decode ceph encoded input"); + ctx->ret = -EINVAL; + return 0; + } + + ctx->script.swap(op.script); + ctx->handler.swap(op.handler); + ctx->input = op.input; + } + break; + + default: + CLS_ERR("error: unknown encoding type"); + ctx->ret = -EFAULT; + ceph_abort(); + return 0; + } + + /* + * Create table to hold registered (valid) handlers. + * + * Must be done before running the script for the first time because the + * script will immediately try to register one or more handlers using + * cls.register(function), which depends on this table. + */ + lua_pushlightuserdata(L, &clslua_registered_handle_reg_key); + lua_newtable(L); + lua_settable(L, LUA_REGISTRYINDEX); + + /* load and compile chunk */ + if (luaL_loadstring(L, ctx->script.c_str())) + return lua_error(L); + + /* execute chunk */ + lua_call(L, 0, 0); + + /* no error, but nothing left to do */ + if (!ctx->handler.size()) { + CLS_LOG(10, "no handler name provided"); + ctx->ret = 0; /* success */ + return 0; + } + + lua_getglobal(L, ctx->handler.c_str()); + if (lua_type(L, -1) != LUA_TFUNCTION) { + CLS_ERR("error: unknown handler or not function: %s", ctx->handler.c_str()); + ctx->ret = -EOPNOTSUPP; + return 0; + } + + /* throw error if function is not registered */ + clslua_check_registered_handler(L); + + /* setup the input/output bufferlists */ + clslua_pushbufferlist(L, &ctx->input); + clslua_pushbufferlist(L, ctx->outbl); + + /* + * Call the target Lua object class handler. If the call is successful then + * we will examine the return value here and store it in the context. Errors + * that occur are handled in the top-level eval() function. + */ + int top = lua_gettop(L); + lua_call(L, 2, LUA_MULTRET); + + /* store return value in context */ + if (!(lua_gettop(L) + 3 - top)) + lua_pushinteger(L, 0); + ctx->ret = luaL_checkinteger(L, -1); + + return 0; +} + +/* + * Main handler. Proxies the Lua VM and the Lua-defined handler. + */ +static int eval_generic(cls_method_context_t hctx, bufferlist *in, bufferlist *out, + InputEncoding in_enc) +{ + struct clslua_hctx ctx; + lua_State *L = NULL; + int ret = -EIO; + + /* stash context for use in Lua VM */ + ctx.hctx = &hctx; + ctx.inbl = in; + ctx.in_enc = in_enc; + ctx.outbl = out; + ctx.error.error = false; + + /* build lua vm state */ + L = luaL_newstate(); + if (!L) { + CLS_ERR("error creating new Lua state"); + goto out; + } + + /* panic handler for unhandled errors */ + lua_atpanic(L, &cls_lua_atpanic); + + if (setjmp(cls_lua_panic_jump) == 0) { + + /* + * Stash the handler context in the register. It contains the objclass + * method context, global error state, and the command and reply structs. + */ + lua_pushlightuserdata(L, &clslua_hctx_reg_key); + lua_pushlightuserdata(L, &ctx); + lua_settable(L, LUA_REGISTRYINDEX); + + /* Process the input and run the script */ + lua_pushcfunction(L, clslua_eval); + ret = lua_pcall(L, 0, 0, 0); + + /* Encountered an error? */ + if (ret) { + struct clslua_err *err = clslua_checkerr(L); + if (!err) { + CLS_ERR("error: cls_lua state machine: unexpected error"); + ceph_abort(); + } + + /* Error origin a cls_cxx_* method? */ + if (err->error) { + ret = err->ret; /* cls_cxx_* return value */ + + /* Errors always abort. Fix up ret and log error */ + if (ret >= 0) { + CLS_ERR("error: unexpected handler return value"); + ret = -EFAULT; + } + + } else + ret = -EIO; /* Generic error code */ + + CLS_ERR("error: %s", lua_tostring(L, -1)); + + } else { + /* + * No Lua error encountered while running the script, but the handler + * may still have returned an error code (e.g. an errno value). + */ + ret = ctx.ret; + } + + } else { + CLS_ERR("error: recovering from Lua panic"); + ret = -EFAULT; + } + +out: + if (L) + lua_close(L); + return ret; +} + +static int eval_json(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + return eval_generic(hctx, in, out, JSON_ENC); +} + +static int eval_bufferlist(cls_method_context_t hctx, bufferlist *in, bufferlist *out) +{ + return eval_generic(hctx, in, out, BUFFERLIST_ENC); +} + +CLS_INIT(lua) +{ + CLS_LOG(20, "Loaded lua class!"); + + cls_handle_t h_class; + cls_method_handle_t h_eval_json; + cls_method_handle_t h_eval_bufferlist; + + cls_register("lua", &h_class); + + cls_register_cxx_method(h_class, "eval_json", + CLS_METHOD_RD | CLS_METHOD_WR, eval_json, &h_eval_json); + + cls_register_cxx_method(h_class, "eval_bufferlist", + CLS_METHOD_RD | CLS_METHOD_WR, eval_bufferlist, &h_eval_bufferlist); +} diff --git a/src/cls/lua/cls_lua.h b/src/cls/lua/cls_lua.h new file mode 100644 index 00000000..70ce9a92 --- /dev/null +++ b/src/cls/lua/cls_lua.h @@ -0,0 +1,14 @@ +#ifndef CEPH_CLS_LUA_H +#define CEPH_CLS_LUA_H + +#include <lua.hpp> +#include "include/types.h" + +#define LOG_LEVEL_DEFAULT 10 + +int luaopen_bufferlist(lua_State *L); + +bufferlist *clslua_checkbufferlist(lua_State *L, int pos = 1); +bufferlist *clslua_pushbufferlist(lua_State *L, bufferlist *set); + +#endif diff --git a/src/cls/lua/cls_lua_client.cc b/src/cls/lua/cls_lua_client.cc new file mode 100644 index 00000000..0e6544a2 --- /dev/null +++ b/src/cls/lua/cls_lua_client.cc @@ -0,0 +1,34 @@ +#include <string> +#include <vector> +#include "include/encoding.h" +#include "include/rados/librados.hpp" // for IoCtx +#include "cls_lua_client.h" +#include "cls_lua_ops.h" + +using std::string; +using std::vector; +using librados::IoCtx; +using librados::bufferlist; + +namespace cls_lua_client { + /* + * Currently the return code and return bufferlist are not wrapped in a + * protocol that allows object class vs Lua to be distinguished. For + * instance, -EOPNOTSUPP might refer to cls_lua not being found, but would + * also be returned when cls_lua is found, but a Lua handler is not found. + */ + int exec(IoCtx& ioctx, const string& oid, const string& script, + const string& handler, bufferlist& input, bufferlist& output) + { + cls_lua_eval_op op; + + op.script = script; + op.handler = handler; + op.input = input; + + bufferlist inbl; + encode(op, inbl); + + return ioctx.exec(oid, "lua", "eval_bufferlist", inbl, output); + } +} diff --git a/src/cls/lua/cls_lua_client.h b/src/cls/lua/cls_lua_client.h new file mode 100644 index 00000000..7e7e164b --- /dev/null +++ b/src/cls/lua/cls_lua_client.h @@ -0,0 +1,13 @@ +#ifndef CLS_LUA_CLIENT_HPP +#define CLS_LUA_CLIENT_HPP +#include <string> + +#include "include/rados/librados.hpp" + +namespace cls_lua_client { + int exec(librados::IoCtx& ioctx, const std::string& oid, + const std::string& script, const std::string& handler, + librados::bufferlist& inbl, librados::bufferlist& outbl); +} + +#endif diff --git a/src/cls/lua/cls_lua_ops.h b/src/cls/lua/cls_lua_ops.h new file mode 100644 index 00000000..c4afbd8a --- /dev/null +++ b/src/cls/lua/cls_lua_ops.h @@ -0,0 +1,31 @@ +#ifndef CEPH_CLS_LUA_OPS_H +#define CEPH_CLS_LUA_OPS_H + +#include <string> + +#include "include/encoding.h" + +struct cls_lua_eval_op { + std::string script; + std::string handler; + bufferlist input; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(script, bl); + encode(handler, bl); + encode(input, bl); + ENCODE_FINISH(bl); + } + + void decode(bufferlist::const_iterator &bl) { + DECODE_START(1, bl); + decode(script, bl); + decode(handler, bl); + decode(input, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(cls_lua_eval_op) + +#endif diff --git a/src/cls/lua/lua_bufferlist.cc b/src/cls/lua/lua_bufferlist.cc new file mode 100644 index 00000000..995b4082 --- /dev/null +++ b/src/cls/lua/lua_bufferlist.cc @@ -0,0 +1,180 @@ +/* + * Lua module wrapping librados::bufferlist + */ +#include <errno.h> +#include <string> +#include <sstream> +#include <math.h> +#include <lua.hpp> +#include "include/types.h" +#include "include/buffer.h" +#include "objclass/objclass.h" +#include "cls/lua/cls_lua.h" + +#define LUA_BUFFERLIST "ClsLua.Bufferlist" + +struct bufferlist_wrap { + bufferlist *bl; + int gc; /* do garbage collect? */ +}; + +static inline struct bufferlist_wrap *to_blwrap(lua_State *L, int pos = 1) +{ + return (bufferlist_wrap *)luaL_checkudata(L, pos, LUA_BUFFERLIST); +} + +bufferlist *clslua_checkbufferlist(lua_State *L, int pos) +{ + struct bufferlist_wrap *blw = to_blwrap(L, pos); + return blw->bl; +} + +/* + * Pushes a new bufferlist userdata object onto the stack. If @set is non-null + * it is assumed to be a bufferlist that should not be garbage collected. + */ +bufferlist *clslua_pushbufferlist(lua_State *L, bufferlist *set) +{ + bufferlist_wrap *blw = static_cast<bufferlist_wrap *>(lua_newuserdata(L, sizeof(*blw))); + blw->bl = set ? set : new bufferlist(); + blw->gc = set ? 0 : 1; + luaL_getmetatable(L, LUA_BUFFERLIST); + lua_setmetatable(L, -2); + return blw->bl; +} + +/* + * Create a new bufferlist + */ +static int bl_new(lua_State *L) +{ + clslua_pushbufferlist(L, NULL); + return 1; +} + +/* + * Convert bufferlist to Lua string + */ +static int bl_str(lua_State *L) +{ + bufferlist *bl = clslua_checkbufferlist(L); + lua_pushlstring(L, bl->c_str(), bl->length()); + return 1; +} + +/* + * Append a Lua string to bufferlist + */ +static int bl_append(lua_State *L) +{ + bufferlist *bl = clslua_checkbufferlist(L); + luaL_checktype(L, 2, LUA_TSTRING); + + size_t len; + const char *data = lua_tolstring(L, 2, &len); + bl->append(data, len); + + return 0; +} + +/* + * Return the length in bytes of bufferlist + */ +static int bl_len(lua_State *L) +{ + bufferlist *bl = clslua_checkbufferlist(L); + lua_pushinteger(L, bl->length()); + return 1; +} + +/* + * Perform byte-for-byte bufferlist equality test + */ +static int bl_eq(lua_State *L) +{ + bufferlist *bl1 = clslua_checkbufferlist(L, 1); + bufferlist *bl2 = clslua_checkbufferlist(L, 2); + lua_pushboolean(L, *bl1 == *bl2 ? 1 : 0); + return 1; +} + +/* + * Bufferlist < operator + */ +static int bl_lt(lua_State *L) +{ + bufferlist *bl1 = clslua_checkbufferlist(L, 1); + bufferlist *bl2 = clslua_checkbufferlist(L, 2); + lua_pushboolean(L, *bl1 < *bl2 ? 1 : 0); + return 1; +} + +/* + * Bufferlist <= operator + */ +static int bl_le(lua_State *L) +{ + bufferlist *bl1 = clslua_checkbufferlist(L, 1); + bufferlist *bl2 = clslua_checkbufferlist(L, 2); + lua_pushboolean(L, *bl1 <= *bl2 ? 1 : 0); + return 1; +} + +/* + * Bufferlist concatentation + */ +static int bl_concat(lua_State *L) +{ + bufferlist *bl1 = clslua_checkbufferlist(L, 1); + bufferlist *bl2 = clslua_checkbufferlist(L, 2); + bufferlist *ret = clslua_pushbufferlist(L, NULL); + ret->append(bl1->c_str(), bl1->length()); + ret->append(bl2->c_str(), bl2->length()); + return 1; +} + +/* + * Garbage collect bufferlist + */ +static int bl_gc(lua_State *L) +{ + struct bufferlist_wrap *blw = to_blwrap(L); + ceph_assert(blw); + ceph_assert(blw->bl); + if (blw->gc) + delete blw->bl; + return 0; +} + +static const struct luaL_Reg bufferlist_methods[] = { + {"str", bl_str}, + {"append", bl_append}, + {"__concat", bl_concat}, + {"__len", bl_len}, + {"__lt", bl_lt}, + {"__le", bl_le}, + {"__gc", bl_gc}, + {"__eq", bl_eq}, + {NULL, NULL} +}; + +static const struct luaL_Reg bllib_f[] = { + {"new", bl_new}, + {NULL, NULL} +}; + +LUALIB_API int luaopen_bufferlist(lua_State *L) +{ + /* Setup bufferlist user-data type */ + luaL_newmetatable(L, LUA_BUFFERLIST); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + + luaL_setfuncs(L, bufferlist_methods, 0); + lua_pop(L, 1); + + lua_newtable(L); + luaL_setfuncs(L, bllib_f, 0); + + return 1; +} |