--- /dev/null
+/*
+ * 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)
+
+cls_handle_t h_class;
+cls_method_handle_t h_eval_json;
+cls_method_handle_t h_eval_bufferlist;
+
+/*
+ * 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 */
+ assert(!lua_isnil(L, -1));
+ 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);
+ 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);
+ 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);
+ 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);
+
+ assert(err);
+ if (err->error) {
+ CLS_ERR("error: cls_lua state machine: unexpected error");
+ assert(0);
+ }
+
+ /* 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;
+ int ret = cls_cxx_map_get_keys(hctx, start_after, max_to_get, &keys);
+ 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;
+ int ret = cls_cxx_map_get_vals(hctx, start_after, filter_prefix,
+ max_to_get, &kvpairs);
+ 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;
+
+ lua_pushnil(L);
+ while (lua_next(L, 1) != 0) {
+ /*
+ * 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;
+
+ lua_pop(L, 1);
+ }
+
+ 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 int const 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 {
+ bufferlist::iterator it = ctx->inbl->begin();
+ ::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;
+ assert(0);
+ 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");
+ assert(0);
+ }
+
+ /* 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);
+}
+
+void __cls_init()
+{
+ CLS_LOG(20, "Loaded lua class!");
+
+ 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);
+}
--- /dev/null
+#include <errno.h>
+#include <lua.hpp>
+#include "include/types.h"
+#include "include/rados/librados.h"
+#include "gtest/gtest.h"
+#include "test/librados/test.h"
+#include "cls/lua/cls_lua_client.h"
+#include "cls/lua/cls_lua.h"
+
+/*
+ * JSON script to test JSON I/O protocol with cls_lua
+ */
+const std::string json_test_script = R"jsonscript(
+{
+ "script": "function json_echo(input, output) output:append(input:str()); end objclass.register(json_echo)",
+ "handler": "json_echo",
+ "input": "omg it works",
+}
+)jsonscript";
+
+/*
+ * Lua test script thanks to the magical c++11 string literal syntax
+ */
+const std::string test_script = R"luascript(
+--
+-- This Lua script file contains all of the handlers used in the cls_lua unit
+-- tests (src/test/cls_lua/test_cls_lua.cc). Each section header corresponds
+-- to the ClsLua.XYZ test.
+--
+
+--
+-- Read
+--
+function read(input, output)
+ size = objclass.stat()
+ bl = objclass.read(0, size)
+ output:append(bl:str())
+end
+
+objclass.register(read)
+
+--
+-- Write
+--
+function write(input, output)
+ objclass.write(0, #input, input)
+end
+
+objclass.register(write)
+
+--
+-- MapGetVal
+--
+function map_get_val_foo(input, output)
+ bl = objclass.map_get_val('foo')
+ output:append(bl:str())
+end
+
+function map_get_val_dne()
+ bl = objclass.map_get_val('dne')
+end
+
+objclass.register(map_get_val_foo)
+objclass.register(map_get_val_dne)
+
+--
+-- Stat
+--
+function stat_ret(input, output)
+ size, mtime = objclass.stat()
+ output:append(size .. "," .. mtime)
+end
+
+function stat_sdne()
+ size, mtime = objclass.stat()
+end
+
+function stat_sdne_pcall()
+ ok, ret, size, mtime = pcall(objclass.stat, o)
+ assert(ok == false)
+ assert(ret == -objclass.ENOENT)
+ return ret
+end
+
+objclass.register(stat_ret)
+objclass.register(stat_sdne)
+objclass.register(stat_sdne_pcall)
+
+--
+-- RetVal
+--
+function rv_h() end
+function rv_h1() return 1; end
+function rv_h0() return 0; end
+function rv_hn1() return -1; end
+function rv_hs1() return '1'; end
+function rv_hs0() return '0'; end
+function rv_hsn1() return '-1'; end
+function rv_hnil() return nil; end
+function rv_ht() return {}; end
+function rv_hstr() return 'asdf'; end
+
+objclass.register(rv_h)
+objclass.register(rv_h1)
+objclass.register(rv_h0)
+objclass.register(rv_hn1)
+objclass.register(rv_hs1)
+objclass.register(rv_hs0)
+objclass.register(rv_hsn1)
+objclass.register(rv_hnil)
+objclass.register(rv_ht)
+objclass.register(rv_hstr)
+
+--
+-- Create
+--
+function create_c() objclass.create(true); end
+function create_cne() objclass.create(false); end
+
+objclass.register(create_c)
+objclass.register(create_cne)
+
+--
+-- Pcall
+--
+function pcall_c() objclass.create(true); end
+
+function pcall_pc()
+ ok, ret = pcall(objclass.create, true)
+ assert(ok == false)
+ assert(ret == -objclass.EEXIST)
+end
+
+function pcall_pcr()
+ ok, ret = pcall(objclass.create, true)
+ assert(ok == false)
+ assert(ret == -objclass.EEXIST)
+ return ret
+end
+
+function pcall_pcr2()
+ ok, ret = pcall(objclass.create, true)
+ assert(ok == false)
+ assert(ret == -objclass.EEXIST)
+ ok, ret = pcall(objclass.create, true)
+ assert(ok == false)
+ assert(ret == -objclass.EEXIST)
+ return -9999
+end
+
+objclass.register(pcall_c)
+objclass.register(pcall_pc)
+objclass.register(pcall_pcr)
+objclass.register(pcall_pcr2)
+
+--
+-- Remove
+--
+function remove_c() objclass.create(true); end
+function remove_r() objclass.remove(); end
+
+objclass.register(remove_c)
+objclass.register(remove_r)
+
+--
+-- MapSetVal
+--
+function map_set_val(input, output)
+ objclass.map_set_val('foo', input)
+end
+
+objclass.register(map_set_val)
+
+--
+-- MapClear
+--
+function map_clear()
+ objclass.map_clear()
+end
+
+objclass.register(map_clear)
+
+--
+-- BufferlistEquality
+--
+function bl_eq_empty_equal(input, output)
+ bl1 = bufferlist.new()
+ bl2 = bufferlist.new()
+ assert(bl1 == bl2)
+end
+
+function bl_eq_empty_selfequal()
+ bl1 = bufferlist.new()
+ assert(bl1 == bl1)
+end
+
+function bl_eq_selfequal()
+ bl1 = bufferlist.new()
+ bl1:append('asdf')
+ assert(bl1 == bl1)
+end
+
+function bl_eq_equal()
+ bl1 = bufferlist.new()
+ bl2 = bufferlist.new()
+ bl1:append('abc')
+ bl2:append('abc')
+ assert(bl1 == bl2)
+end
+
+function bl_eq_notequal()
+ bl1 = bufferlist.new()
+ bl2 = bufferlist.new()
+ bl1:append('abc')
+ bl2:append('abcd')
+ assert(bl1 ~= bl2)
+end
+
+objclass.register(bl_eq_empty_equal)
+objclass.register(bl_eq_empty_selfequal)
+objclass.register(bl_eq_selfequal)
+objclass.register(bl_eq_equal)
+objclass.register(bl_eq_notequal)
+
+--
+-- Bufferlist Compare
+--
+function bl_lt()
+ local a = bufferlist.new()
+ local b = bufferlist.new()
+ a:append('A')
+ b:append('B')
+ assert(a < b)
+end
+
+function bl_le()
+ local a = bufferlist.new()
+ local b = bufferlist.new()
+ a:append('A')
+ b:append('B')
+ assert(a <= b)
+end
+
+objclass.register(bl_lt)
+objclass.register(bl_le)
+
+--
+-- Bufferlist concat
+--
+function bl_concat_eq()
+ local a = bufferlist.new()
+ local b = bufferlist.new()
+ local ab = bufferlist.new()
+ a:append('A')
+ b:append('B')
+ ab:append('AB')
+ assert(a .. b == ab)
+end
+
+function bl_concat_ne()
+ local a = bufferlist.new()
+ local b = bufferlist.new()
+ local ab = bufferlist.new()
+ a:append('A')
+ b:append('B')
+ ab:append('AB')
+ assert(b .. a ~= ab)
+end
+
+function bl_concat_immut()
+ local a = bufferlist.new()
+ local b = bufferlist.new()
+ local ab = bufferlist.new()
+ a:append('A')
+ b:append('B')
+ ab:append('AB')
+ x = a .. b
+ assert(x == ab)
+ b:append('C')
+ assert(x == ab)
+ local bc = bufferlist.new()
+ bc:append('BC')
+ assert(b == bc)
+end
+
+objclass.register(bl_concat_eq)
+objclass.register(bl_concat_ne)
+objclass.register(bl_concat_immut)
+
+--
+-- RunError
+--
+function runerr_a()
+ error('WTF')
+end
+
+function runerr_b()
+ runerr_a()
+end
+
+function runerr_c()
+ runerr_b()
+end
+
+-- only runerr_c is called
+objclass.register(runerr_c)
+
+--
+-- GetXattr
+--
+function getxattr(input, output)
+ bl = objclass.getxattr("fooz")
+ output:append(bl:str())
+end
+
+objclass.register(getxattr)
+
+--
+-- SetXattr
+--
+function setxattr(input, output)
+ objclass.setxattr("fooz2", input)
+end
+
+objclass.register(setxattr)
+
+--
+-- WriteFull
+--
+function write_full(input, output)
+ objclass.write_full(input)
+end
+
+objclass.register(write_full)
+
+--
+-- GetXattrs
+--
+function getxattrs(input, output)
+ -- result
+ xattrs = objclass.getxattrs()
+
+ -- sort for determisitic test
+ arr = {}
+ for n in pairs(xattrs) do
+ table.insert(arr, n)
+ end
+ table.sort(arr)
+
+ output_str = ""
+ for i,key in ipairs(arr) do
+ output_str = output_str .. key .. "/" .. xattrs[key]:str() .. "/"
+ end
+ output:append(output_str)
+end
+
+objclass.register(getxattrs)
+
+--
+-- MapGetKeys
+--
+function map_get_keys(input, output)
+ -- result
+ keys = objclass.map_get_keys("", 5)
+
+ -- sort for determisitic test
+ arr = {}
+ for n in pairs(keys) do
+ table.insert(arr, n)
+ end
+ table.sort(arr)
+
+ output_str = ""
+ for i,key in ipairs(arr) do
+ output_str = output_str .. key .. "/"
+ end
+
+ output:append(output_str)
+end
+
+objclass.register(map_get_keys)
+
+--
+-- MapGetVals
+--
+function map_get_vals(input, output)
+ -- result
+ kvs = objclass.map_get_vals("", "", 10)
+
+ -- sort for determisitic test
+ arr = {}
+ for n in pairs(kvs) do
+ table.insert(arr, n)
+ end
+ table.sort(arr)
+
+ output_str = ""
+ for i,key in ipairs(arr) do
+ output_str = output_str .. key .. "/" .. kvs[key]:str() .. "/"
+ end
+ output:append(output_str)
+end
+
+objclass.register(map_get_vals)
+
+--
+-- MapHeader (write)
+--
+function map_write_header(input, output)
+ objclass.map_write_header(input)
+end
+
+objclass.register(map_write_header)
+
+--
+-- MapHeader (read)
+--
+function map_read_header(input, output)
+ hdr = objclass.map_read_header()
+ output:append(hdr:str())
+end
+
+objclass.register(map_read_header)
+
+--
+-- MapSetVals
+--
+function map_set_vals_empty(input, output)
+ record = {}
+ objclass.map_set_vals(record)
+end
+
+function map_set_vals_one(input, output)
+ record = {
+ a = "a_val"
+ }
+ objclass.map_set_vals(record)
+end
+
+function map_set_vals_two(input, output)
+ record = {
+ a = "a_val",
+ b = "b_val"
+ }
+ objclass.map_set_vals(record)
+end
+
+function map_set_vals_three(input, output)
+ record = {
+ a = "a_val",
+ b = "b_val",
+ c = "c_val"
+ }
+ objclass.map_set_vals(record)
+end
+
+function map_set_vals_array(input, output)
+ array = {}
+ array[1] = "1_val"
+ array[2] = "2_val"
+ objclass.map_set_vals(array)
+end
+
+function map_set_vals_mixed(input, output)
+ record = {
+ a = "a_val",
+ b = "b_val"
+ }
+ record[1] = "1_val"
+ record[2] = "2_val"
+ objclass.map_set_vals(record)
+end
+
+function map_set_vals_bad_val(input, output)
+ record = {
+ a = {}
+ }
+ objclass.map_set_vals(record)
+end
+
+objclass.register(map_set_vals_empty)
+objclass.register(map_set_vals_one)
+objclass.register(map_set_vals_two)
+objclass.register(map_set_vals_three)
+objclass.register(map_set_vals_array)
+objclass.register(map_set_vals_mixed)
+objclass.register(map_set_vals_bad_val)
+
+--
+-- MapRemoveKey
+--
+function map_remove_key(input, output)
+ objclass.map_remove_key("a")
+end
+
+objclass.register(map_remove_key)
+
+--
+-- Version/Subop
+--
+function current_version(input, output)
+ ret = objclass.current_version()
+ output:append("" .. ret)
+ objclass.log(0, ret)
+end
+
+function current_subop_num(input, output)
+ ret = objclass.current_subop_num()
+ output:append("" .. ret)
+ objclass.log(0, ret)
+end
+
+function current_subop_version(input, output)
+ ret = objclass.current_subop_version()
+ output:append("" .. ret)
+ objclass.log(0, ret)
+end
+
+objclass.register(current_version)
+objclass.register(current_subop_num)
+objclass.register(current_subop_version)
+
+)luascript";
+
+/*
+ * Test harness uses single pool for the entire test case, and generates
+ * unique object names for each test.
+ */
+class ClsLua : public ::testing::Test {
+ protected:
+ static void SetUpTestCase() {
+ pool_name = get_temp_pool_name();
+ ASSERT_EQ("", create_one_pool_pp(pool_name, rados));
+ ASSERT_EQ(0, rados.ioctx_create(pool_name.c_str(), ioctx));
+ }
+
+ static void TearDownTestCase() {
+ ioctx.close();
+ ASSERT_EQ(0, destroy_one_pool_pp(pool_name, rados));
+ }
+
+ virtual void SetUp() {
+ /* Grab test names to build unique objects */
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+
+ /* Create unique string using test/testname/pid */
+ std::stringstream ss_oid;
+ ss_oid << test_info->test_case_name() << "_" <<
+ test_info->name() << "_" << getpid();
+
+ /* Unique object for test to use */
+ oid = ss_oid.str();
+ }
+
+ virtual void TearDown() {
+ }
+
+ /*
+ * Helper function. This functionality should eventually make its way into
+ * a clslua client library of some sort.
+ */
+ int __clslua_exec(const string& oid, const string& script,
+ librados::bufferlist *input = NULL, const string& funcname = "")
+ {
+ bufferlist inbl;
+ if (input)
+ inbl = *input;
+
+ reply_output.clear();
+
+ return cls_lua_client::exec(ioctx, oid, script, funcname, inbl,
+ reply_output);
+ }
+
+ int clslua_exec(const string& script, librados::bufferlist *input = NULL,
+ const string& funcname = "")
+ {
+ return __clslua_exec(oid, script, input, funcname);
+ }
+
+ static librados::Rados rados;
+ static librados::IoCtx ioctx;
+ static string pool_name;
+
+ string oid;
+ bufferlist reply_output;
+};
+
+librados::Rados ClsLua::rados;
+librados::IoCtx ClsLua::ioctx;
+string ClsLua::pool_name;
+
+TEST_F(ClsLua, Write) {
+ /* write some data into object */
+ string written = "Hello World";
+ bufferlist inbl;
+ ::encode(written, inbl);
+ ASSERT_EQ(0, clslua_exec(test_script, &inbl, "write"));
+
+ /* have Lua read out of the object */
+ uint64_t size;
+ bufferlist outbl;
+ ASSERT_EQ(0, ioctx.stat(oid, &size, NULL));
+ ASSERT_EQ(size, (uint64_t)ioctx.read(oid, outbl, size, 0) );
+
+ /* compare what Lua read to what we wrote */
+ string read;
+ ::decode(read, outbl);
+ ASSERT_EQ(read, written);
+}
+
+TEST_F(ClsLua, SyntaxError) {
+ ASSERT_EQ(-EIO, clslua_exec("-"));
+}
+
+TEST_F(ClsLua, EmptyScript) {
+ ASSERT_EQ(0, clslua_exec(""));
+}
+
+TEST_F(ClsLua, RetVal) {
+ /* handlers can return numeric values */
+ ASSERT_EQ(1, clslua_exec(test_script, NULL, "rv_h1"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "rv_h0"));
+ ASSERT_EQ(-1, clslua_exec(test_script, NULL, "rv_hn1"));
+ ASSERT_EQ(1, clslua_exec(test_script, NULL, "rv_hs1"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "rv_hs0"));
+ ASSERT_EQ(-1, clslua_exec(test_script, NULL, "rv_hsn1"));
+
+ /* no return value is success */
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "rv_h"));
+
+ /* non-numeric return values are errors */
+ ASSERT_EQ(-EIO, clslua_exec(test_script, NULL, "rv_hnil"));
+ ASSERT_EQ(-EIO, clslua_exec(test_script, NULL, "rv_ht"));
+ ASSERT_EQ(-EIO, clslua_exec(test_script, NULL, "rv_hstr"));
+}
+
+TEST_F(ClsLua, Create) {
+ /* create works */
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "create_c"));
+
+ /* exclusive works */
+ ASSERT_EQ(-EEXIST, clslua_exec(test_script, NULL, "create_c"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "create_cne"));
+}
+
+TEST_F(ClsLua, Pcall) {
+ /* create and error works */
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "pcall_c"));
+ ASSERT_EQ(-EEXIST, clslua_exec(test_script, NULL, "pcall_c"));
+
+ /* pcall masks the error */
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "pcall_pc"));
+
+ /* pcall lets us get the failed return value */
+ ASSERT_EQ(-EEXIST, clslua_exec(test_script, NULL, "pcall_pcr"));
+
+ /*
+ * the first call in pcr2 will fail (check ret != 0), and the second pcall
+ * should also fail (we check with a bogus return value to mask real
+ * errors). This is also an important check for our error handling because
+ * we need a case where two functions in the same handler fail to exercise
+ * our internal error book keeping.
+ */
+ ASSERT_EQ(-9999, clslua_exec(test_script, NULL, "pcall_pcr2"));
+}
+
+TEST_F(ClsLua, Remove) {
+ /* object doesn't exist */
+ ASSERT_EQ(-ENOENT, clslua_exec(test_script, NULL, "remove_r"));
+
+ /* can remove */
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "remove_c"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "remove_r"));
+ ASSERT_EQ(-ENOENT, clslua_exec(test_script, NULL, "remove_r"));
+}
+
+TEST_F(ClsLua, Stat) {
+ /* build object and stat */
+ char buf[1024];
+ bufferlist bl;
+ bl.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write_full(oid, bl));
+ uint64_t size;
+ time_t mtime;
+ ASSERT_EQ(0, ioctx.stat(oid, &size, &mtime));
+
+ /* test stat success */
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "stat_ret"));
+
+ // size,mtime from ioctx call
+ std::stringstream s1;
+ s1 << size << "," << mtime;
+
+ // lua constructed size,mtime string
+ std::string s2(reply_output.c_str(), reply_output.length());
+
+ ASSERT_EQ(s1.str(), s2);
+
+ /* test object dne */
+ ASSERT_EQ(-ENOENT, __clslua_exec("dne", test_script, NULL, "stat_sdne"));
+
+ /* can capture error with pcall */
+ ASSERT_EQ(-ENOENT, __clslua_exec("dne", test_script, NULL, "stat_sdne_pcall"));
+}
+
+TEST_F(ClsLua, MapClear) {
+ /* write some data into a key */
+ string msg = "This is a test message";
+ bufferlist val;
+ val.append(msg.c_str(), msg.size());
+ map<string, bufferlist> map;
+ map["foo"] = val;
+ ASSERT_EQ(0, ioctx.omap_set(oid, map));
+
+ /* test we can get it back out */
+ set<string> keys;
+ keys.insert("foo");
+ map.clear();
+ ASSERT_EQ(0, (int)map.count("foo"));
+ ASSERT_EQ(0, ioctx.omap_get_vals_by_keys(oid, keys, &map));
+ ASSERT_EQ(1, (int)map.count("foo"));
+
+ /* now clear it */
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_clear"));
+
+ /* test that the map we get back is empty now */
+ map.clear();
+ ASSERT_EQ(0, (int)map.count("foo"));
+ ASSERT_EQ(0, ioctx.omap_get_vals_by_keys(oid, keys, &map));
+ ASSERT_EQ(0, (int)map.count("foo"));
+}
+
+TEST_F(ClsLua, MapSetVal) {
+ /* build some input value */
+ bufferlist orig_val;
+ ::encode("this is the original value yay", orig_val);
+
+ /* have the lua script stuff the data into a map value */
+ ASSERT_EQ(0, clslua_exec(test_script, &orig_val, "map_set_val"));
+
+ /* grap the key now and compare to orig */
+ map<string, bufferlist> out_map;
+ set<string> out_keys;
+ out_keys.insert("foo");
+ ASSERT_EQ(0, ioctx.omap_get_vals_by_keys(oid, out_keys, &out_map));
+ bufferlist out_bl = out_map["foo"];
+ string out_val;
+ ::decode(out_val, out_bl);
+ ASSERT_EQ(out_val, "this is the original value yay");
+}
+
+TEST_F(ClsLua, MapGetVal) {
+ /* write some data into a key */
+ string msg = "This is a test message";
+ bufferlist orig_val;
+ orig_val.append(msg.c_str(), msg.size());
+ map<string, bufferlist> orig_map;
+ orig_map["foo"] = orig_val;
+ ASSERT_EQ(0, ioctx.omap_set(oid, orig_map));
+
+ /* now compare to what we put it */
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_val_foo"));
+
+ /* check return */
+ string ret_val;
+ ret_val.assign(reply_output.c_str(), reply_output.length());
+ ASSERT_EQ(ret_val, msg);
+
+ /* error case */
+ ASSERT_EQ(-ENOENT, clslua_exec(test_script, NULL, "map_get_val_dne"));
+}
+
+TEST_F(ClsLua, Read) {
+ /* put data into object */
+ string msg = "This is a test message";
+ bufferlist bl;
+ bl.append(msg.c_str(), msg.size());
+ ASSERT_EQ(0, ioctx.write_full(oid, bl));
+
+ /* get lua to read it and send it back */
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "read"));
+
+ /* check return */
+ string ret_val;
+ ret_val.assign(reply_output.c_str(), reply_output.length());
+ ASSERT_EQ(ret_val, msg);
+}
+
+TEST_F(ClsLua, Log) {
+ ASSERT_EQ(0, clslua_exec("objclass.log()"));
+ ASSERT_EQ(0, clslua_exec("s = objclass.log(); objclass.log(s);"));
+ ASSERT_EQ(0, clslua_exec("objclass.log(1)"));
+ ASSERT_EQ(0, clslua_exec("objclass.log(-1)"));
+ ASSERT_EQ(0, clslua_exec("objclass.log('x')"));
+ ASSERT_EQ(0, clslua_exec("objclass.log(0, 0)"));
+ ASSERT_EQ(0, clslua_exec("objclass.log(1, 1)"));
+ ASSERT_EQ(0, clslua_exec("objclass.log(-10, -10)"));
+ ASSERT_EQ(0, clslua_exec("objclass.log('x', 'y')"));
+ ASSERT_EQ(0, clslua_exec("objclass.log(1, 'one')"));
+ ASSERT_EQ(0, clslua_exec("objclass.log(1, 'one', 'two')"));
+ ASSERT_EQ(0, clslua_exec("objclass.log('one', 'two', 'three')"));
+ ASSERT_EQ(0, clslua_exec("s = objclass.log('one', 'two', 'three'); objclass.log(s);"));
+}
+
+TEST_F(ClsLua, BufferlistEquality) {
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_eq_empty_equal"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_eq_empty_selfequal"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_eq_selfequal"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_eq_equal"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_eq_notequal"));
+}
+
+TEST_F(ClsLua, RunError) {
+ ASSERT_EQ(-EIO, clslua_exec(test_script, NULL, "runerr_c"));
+}
+
+TEST_F(ClsLua, HandleNotFunc) {
+ string script = "x = 1;";
+ ASSERT_EQ(-EOPNOTSUPP, clslua_exec(script, NULL, "x"));
+}
+
+TEST_F(ClsLua, Register) {
+ /* normal cases: register and maybe call the handler */
+ string script = "function h() end; objclass.register(h);";
+ ASSERT_EQ(0, clslua_exec(script, NULL, ""));
+ ASSERT_EQ(0, clslua_exec(script, NULL, "h"));
+
+ /* can register and call multiple handlers */
+ script = "function h1() end; function h2() end;"
+ "objclass.register(h1); objclass.register(h2);";
+ ASSERT_EQ(0, clslua_exec(script, NULL, ""));
+ ASSERT_EQ(0, clslua_exec(script, NULL, "h1"));
+ ASSERT_EQ(0, clslua_exec(script, NULL, "h2"));
+
+ /* normal cases: register before function is defined */
+ script = "objclass.register(h); function h() end;";
+ ASSERT_EQ(-EIO, clslua_exec(script, NULL, ""));
+ ASSERT_EQ(-EIO, clslua_exec(script, NULL, "h"));
+
+ /* cannot call handler that isn't registered */
+ script = "function h() end;";
+ ASSERT_EQ(-EIO, clslua_exec(script, NULL, "h"));
+
+ /* handler doesn't exist */
+ script = "objclass.register(lalala);";
+ ASSERT_EQ(-EIO, clslua_exec(script, NULL, ""));
+
+ /* handler isn't a function */
+ script = "objclass.register('some string');";
+ ASSERT_EQ(-EIO, clslua_exec(script, NULL, ""));
+
+ /* cannot register handler multiple times */
+ script = "function h() end; objclass.register(h); objclass.register(h);";
+ ASSERT_EQ(-EIO, clslua_exec(script, NULL, ""));
+}
+
+TEST_F(ClsLua, BufferlistCompare) {
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_lt"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_le"));
+}
+
+TEST_F(ClsLua, BufferlistConcat) {
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_concat_eq"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_concat_ne"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "bl_concat_immut"));
+}
+
+TEST_F(ClsLua, GetXattr) {
+ bufferlist bl;
+ bl.append("blahblahblahblahblah");
+ ASSERT_EQ(0, ioctx.setxattr(oid, "fooz", bl));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "getxattr"));
+ ASSERT_TRUE(reply_output == bl);
+}
+
+TEST_F(ClsLua, SetXattr) {
+ bufferlist inbl;
+ inbl.append("blahblahblahblahblah");
+ ASSERT_EQ(0, clslua_exec(test_script, &inbl, "setxattr"));
+ bufferlist outbl;
+ ASSERT_EQ((int)inbl.length(), ioctx.getxattr(oid, "fooz2", outbl));
+ ASSERT_TRUE(outbl == inbl);
+}
+
+TEST_F(ClsLua, WriteFull) {
+ // write some data
+ char buf[1024];
+ bufferlist blin;
+ blin.append(buf, sizeof(buf));
+ ASSERT_EQ(0, ioctx.write(oid, blin, blin.length(), 0));
+ bufferlist blout;
+ ASSERT_EQ((int)blin.length(), ioctx.read(oid, blout, 0, 0));
+ ASSERT_EQ(blin, blout);
+
+ // execute write_full from lua
+ blin.clear();
+ char buf2[200];
+ sprintf(buf2, "%s", "data replacing content");
+ blin.append(buf2, sizeof(buf2));
+ ASSERT_EQ(0, clslua_exec(test_script, &blin, "write_full"));
+
+ // read it back
+ blout.clear();
+ ASSERT_EQ((int)blin.length(), ioctx.read(oid, blout, 0, 0));
+ ASSERT_EQ(blin, blout);
+}
+
+TEST_F(ClsLua, GetXattrs) {
+ ASSERT_EQ(0, ioctx.create(oid, false));
+
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "getxattrs"));
+ ASSERT_EQ(0, (int)reply_output.length());
+
+ string key1str("key1str");
+ bufferlist key1bl;
+ key1bl.append(key1str);
+ ASSERT_EQ(0, ioctx.setxattr(oid, "key1", key1bl));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "getxattrs"));
+ string out1(reply_output.c_str(), reply_output.length()); // add the trailing \0
+ ASSERT_STREQ(out1.c_str(), "key1/key1str/");
+
+ string key2str("key2str");
+ bufferlist key2bl;
+ key2bl.append(key2str);
+ ASSERT_EQ(0, ioctx.setxattr(oid, "key2", key2bl));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "getxattrs"));
+ string out2(reply_output.c_str(), reply_output.length()); // add the trailing \0
+ ASSERT_STREQ(out2.c_str(), "key1/key1str/key2/key2str/");
+
+ string key3str("key3str");
+ bufferlist key3bl;
+ key3bl.append(key3str);
+ ASSERT_EQ(0, ioctx.setxattr(oid, "key3", key3bl));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "getxattrs"));
+ string out3(reply_output.c_str(), reply_output.length()); // add the trailing \0
+ ASSERT_STREQ(out3.c_str(), "key1/key1str/key2/key2str/key3/key3str/");
+}
+
+TEST_F(ClsLua, MapGetKeys) {
+ ASSERT_EQ(0, ioctx.create(oid, false));
+
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_keys"));
+ ASSERT_EQ(0, (int)reply_output.length());
+
+ map<string, bufferlist> kvpairs;
+
+ kvpairs["k1"] = bufferlist();
+ ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_keys"));
+ string out1(reply_output.c_str(), reply_output.length()); // add the trailing \0
+ ASSERT_STREQ(out1.c_str(), "k1/");
+
+ kvpairs["k2"] = bufferlist();
+ ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_keys"));
+ string out2(reply_output.c_str(), reply_output.length()); // add the trailing \0
+ ASSERT_STREQ(out2.c_str(), "k1/k2/");
+
+ kvpairs["xxx"] = bufferlist();
+ ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_keys"));
+ string out3(reply_output.c_str(), reply_output.length()); // add the trailing \0
+ ASSERT_STREQ(out3.c_str(), "k1/k2/xxx/");
+}
+
+TEST_F(ClsLua, MapGetVals) {
+ ASSERT_EQ(0, ioctx.create(oid, false));
+
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_vals"));
+ ASSERT_EQ(0, (int)reply_output.length());
+
+ map<string, bufferlist> kvpairs;
+
+ kvpairs["key1"] = bufferlist();
+ kvpairs["key1"].append(string("key1str"));
+ ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_vals"));
+ string out1(reply_output.c_str(), reply_output.length()); // add the trailing \0
+ ASSERT_STREQ(out1.c_str(), "key1/key1str/");
+
+ kvpairs["key2"] = bufferlist();
+ kvpairs["key2"].append(string("key2str"));
+ ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_vals"));
+ string out2(reply_output.c_str(), reply_output.length()); // add the trailing \0
+ ASSERT_STREQ(out2.c_str(), "key1/key1str/key2/key2str/");
+
+ kvpairs["key3"] = bufferlist();
+ kvpairs["key3"].append(string("key3str"));
+ ASSERT_EQ(0, ioctx.omap_set(oid, kvpairs));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_get_vals"));
+ string out3(reply_output.c_str(), reply_output.length()); // add the trailing \0
+ ASSERT_STREQ(out3.c_str(), "key1/key1str/key2/key2str/key3/key3str/");
+}
+
+TEST_F(ClsLua, MapHeader) {
+ ASSERT_EQ(0, ioctx.create(oid, false));
+
+ bufferlist bl_out;
+ ASSERT_EQ(0, ioctx.omap_get_header(oid, &bl_out));
+ ASSERT_EQ(0, (int)bl_out.length());
+
+ std::string val("this is a value");
+ bufferlist hdr;
+ hdr.append(val);
+
+ ASSERT_EQ(0, clslua_exec(test_script, &hdr, "map_write_header"));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_read_header"));
+
+ ASSERT_EQ(reply_output, hdr);
+}
+
+TEST_F(ClsLua, MapSetVals) {
+ ASSERT_EQ(0, ioctx.create(oid, false));
+
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_empty"));
+
+ std::map<string, bufferlist> out_vals;
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_one"));
+ ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals));
+ ASSERT_EQ(1, (int)out_vals.size());
+ ASSERT_STREQ("a_val", std::string(out_vals["a"].c_str(), out_vals["a"].length()).c_str());
+
+ out_vals.clear();
+ ASSERT_EQ(0, ioctx.omap_clear(oid));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_two"));
+ ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals));
+ ASSERT_EQ(2, (int)out_vals.size());
+ ASSERT_STREQ("a_val", std::string(out_vals["a"].c_str(), out_vals["a"].length()).c_str());
+ ASSERT_STREQ("b_val", std::string(out_vals["b"].c_str(), out_vals["b"].length()).c_str());
+
+ out_vals.clear();
+ ASSERT_EQ(0, ioctx.omap_clear(oid));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_three"));
+ ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals));
+ ASSERT_EQ(3, (int)out_vals.size());
+ ASSERT_STREQ("a_val", std::string(out_vals["a"].c_str(), out_vals["a"].length()).c_str());
+ ASSERT_STREQ("b_val", std::string(out_vals["b"].c_str(), out_vals["b"].length()).c_str());
+ ASSERT_STREQ("c_val", std::string(out_vals["c"].c_str(), out_vals["c"].length()).c_str());
+
+ out_vals.clear();
+ ASSERT_EQ(0, ioctx.omap_clear(oid));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_array"));
+ ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals));
+ ASSERT_EQ(2, (int)out_vals.size());
+ ASSERT_STREQ("1_val", std::string(out_vals["1"].c_str(), out_vals["1"].length()).c_str());
+ ASSERT_STREQ("2_val", std::string(out_vals["2"].c_str(), out_vals["2"].length()).c_str());
+
+ out_vals.clear();
+ ASSERT_EQ(0, ioctx.omap_clear(oid));
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_mixed"));
+ ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals));
+ ASSERT_EQ(4, (int)out_vals.size());
+ ASSERT_STREQ("a_val", std::string(out_vals["a"].c_str(), out_vals["a"].length()).c_str());
+ ASSERT_STREQ("b_val", std::string(out_vals["b"].c_str(), out_vals["b"].length()).c_str());
+ ASSERT_STREQ("1_val", std::string(out_vals["1"].c_str(), out_vals["1"].length()).c_str());
+ ASSERT_STREQ("2_val", std::string(out_vals["2"].c_str(), out_vals["2"].length()).c_str());
+
+ ASSERT_EQ(-EINVAL, clslua_exec(test_script, NULL, "map_set_vals_bad_val"));
+}
+
+TEST_F(ClsLua, MapRemoveKey) {
+ ASSERT_EQ(0, ioctx.create(oid, false));
+
+ std::map<string, bufferlist> out_vals;
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_set_vals_two"));
+ ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals));
+ ASSERT_EQ(2, (int)out_vals.size());
+ ASSERT_STREQ("a_val", std::string(out_vals["a"].c_str(), out_vals["a"].length()).c_str());
+ ASSERT_STREQ("b_val", std::string(out_vals["b"].c_str(), out_vals["b"].length()).c_str());
+
+ out_vals.clear();
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "map_remove_key"));
+ ASSERT_EQ(0, ioctx.omap_get_vals(oid, "", 100, &out_vals));
+ ASSERT_EQ(1, (int)out_vals.size());
+ ASSERT_STREQ("b_val", std::string(out_vals["b"].c_str(), out_vals["b"].length()).c_str());
+}
+
+TEST_F(ClsLua, VersionSubop) {
+ ASSERT_EQ(0, ioctx.create(oid, false));
+
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "current_version"));
+ ASSERT_GT((int)reply_output.length(), 0);
+
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "current_subop_num"));
+ ASSERT_GT((int)reply_output.length(), 0);
+
+ ASSERT_EQ(0, clslua_exec(test_script, NULL, "current_subop_version"));
+ ASSERT_GT((int)reply_output.length(), 0);
+}
+
+TEST_F(ClsLua, Json) {
+ ASSERT_EQ(0, ioctx.create(oid, false));
+
+ bufferlist inbl, outbl;
+
+ inbl.append(json_test_script);
+
+ int ret = ioctx.exec(oid, "lua", "eval_json", inbl, outbl);
+ ASSERT_EQ(ret, 0);
+
+ std::string out(outbl.c_str(), outbl.length());
+ ASSERT_STREQ(out.c_str(), "omg it works");
+}