/* * Copyright (C) 2016 "IoT.bzh" * Author Fulup Ar Foll * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ref: * (manual) https://www.lua.org/manual/5.3/manual.html * (lua->C) http://www.troubleshooters.com/codecorn/lua/lua_c_calls_lua.htm#_Anatomy_of_a_Lua_Call * (lua/C Var) http://acamara.es/blog/2012/08/passing-variables-from-lua-5-2-to-c-and-vice-versa/ * (Lua/C Lib)https://john.nachtimwald.com/2014/07/12/wrapping-a-c-library-in-lua/ * (Lua/C Table) https://gist.github.com/SONIC3D/10388137 * (Lua/C Nested table) https://stackoverflow.com/questions/45699144/lua-nested-table-from-lua-to-c * (Lua/C Wrapper) https://stackoverflow.com/questions/45699950/lua-passing-handle-to-function-created-with-newlib * */ #define _GNU_SOURCE #include #include #include #include "ctl-config.h" #define LUA_FIRST_ARG 2 // when using luaL_newlib callback receive libtable as 1st arg #define LUA_MSG_MAX_LENGTH 2048 #define JSON_ERROR (json_object*)-1 #define LUA_PATH_VALUE "package.path = package.path .. ';?.lua;" #ifndef LUA_GLOB_PATTERN #define LUA_GLOB_PATTERN "/?.lua;" #endif static lua_State* luaState; CtlPluginT *ctlPlugins = NULL; #if CTX_MAGIC_VALUE static int CTX_MAGIC = CTX_MAGIC_VALUE; #else static int CTX_MAGIC; #endif typedef struct { char *name; int count; afb_event_t event; } LuaAfbEvent; typedef struct { int ctxMagic; CtlSourceT *source; } LuaAfbSourceT; typedef struct { const char *callback; json_object *context; CtlSourceT *source; } LuaCbHandleT; /* * Note(Fulup): I fail to use luaL_setmetatable and replaced it with a simple opaque * handle while waiting for someone smarter than me to find a better solution * https://stackoverflow.com/questions/45596493/lua-using-lua-newuserdata-from-lua-pcall */ static CtlSourceT *LuaSourcePop(lua_State *luaState, int index) { LuaAfbSourceT *afbSource; luaL_checktype(luaState, index, LUA_TLIGHTUSERDATA); afbSource = (LuaAfbSourceT *) lua_touserdata(luaState, index); if (afbSource == NULL || afbSource->ctxMagic != CTX_MAGIC) { luaL_error(luaState, "(Hoops) Invalid source handle"); return NULL; } return afbSource->source; } static LuaAfbSourceT *LuaSourcePush(lua_State *luaState, CtlSourceT *source) { LuaAfbSourceT *afbSource = (LuaAfbSourceT *) calloc(1, sizeof (LuaAfbSourceT)); if (!afbSource) { AFB_API_ERROR(source->api, "LuaSourcePush fails to allocate user data context"); return NULL; } lua_pushlightuserdata(luaState, afbSource); afbSource->ctxMagic = CTX_MAGIC; afbSource->source = source; return afbSource; } // Push a json structure on the stack as a LUA table static int LuaPushArgument(CtlSourceT *source, json_object *argsJ) { //AFB_NOTICE("LuaPushArgument argsJ=%s", json_object_to_json_string(argsJ)); json_type jtype = json_object_get_type(argsJ); switch (jtype) { case json_type_object: { lua_newtable(luaState); json_object_object_foreach(argsJ, key, val) { int done = LuaPushArgument(source, val); if (done) { lua_setfield(luaState, -2, key); } } break; } case json_type_array: { int length = (int)json_object_array_length(argsJ); lua_newtable(luaState); for (int idx = 0; idx < length; idx++) { json_object *val = json_object_array_get_idx(argsJ, idx); LuaPushArgument(source, val); lua_seti(luaState, -2, idx); } break; } case json_type_int: lua_pushinteger(luaState, json_object_get_int64(argsJ)); break; case json_type_string: lua_pushstring(luaState, json_object_get_string(argsJ)); break; case json_type_boolean: lua_pushboolean(luaState, json_object_get_boolean(argsJ)); break; case json_type_double: lua_pushnumber(luaState, json_object_get_double(argsJ)); break; case json_type_null: AFB_API_NOTICE(source->api, "LuaPushArgument: NULL object type %s", json_object_to_json_string(argsJ)); lua_pushnil(luaState); break; default: AFB_API_ERROR(source->api, "LuaPushArgument: unsupported Json object type %s", json_object_to_json_string(argsJ)); return 0; } return 1; } static json_object *LuaPopOneArg(CtlSourceT *source, lua_State* luaState, int idx); // Move a table from Internal Lua representation to Json one // Numeric table are transformed in json array, string one in object // Mix numeric/string key are not supported static json_object *LuaTableToJson(CtlSourceT *source, lua_State* luaState, int index) { #define LUA_KEY_INDEX -2 #define LUA_VALUE_INDEX -1 int idx; int tableType; json_object *tableJ = NULL; lua_pushnil(luaState); // 1st key if (index < 0) index--; for (idx = 1; lua_next(luaState, index) != 0; idx++) { // uses 'key' (at index -2) and 'value' (at index -1) if (lua_type(luaState, LUA_KEY_INDEX) == LUA_TSTRING) { if (!tableJ) { tableJ = json_object_new_object(); tableType = LUA_TSTRING; } else if (tableType != LUA_TSTRING) { AFB_API_ERROR(source->api, "MIX Lua Table with key string/numeric not supported"); return NULL; } const char *key = lua_tostring(luaState, LUA_KEY_INDEX); json_object *argJ = LuaPopOneArg(source, luaState, LUA_VALUE_INDEX); json_object_object_add(tableJ, key, argJ); } else { if (!tableJ) { tableJ = json_object_new_array(); tableType = LUA_TNUMBER; } else if (tableType != LUA_TNUMBER) { AFB_API_ERROR(source->api, "MIX Lua Table with key numeric/string not supported"); return NULL; } json_object *argJ = LuaPopOneArg(source, luaState, LUA_VALUE_INDEX); json_object_array_add(tableJ, argJ); } lua_pop(luaState, 1); // removes 'value'; keeps 'key' for next iteration } // Query is empty free empty json object if (idx == 1) { json_object_put(tableJ); return NULL; } return tableJ; } static json_object *LuaPopOneArg(CtlSourceT *source, lua_State* luaState, int idx) { json_object *value = NULL; int luaType = lua_type(luaState, idx); switch (luaType) { case LUA_TNUMBER: { lua_Number number = lua_tonumber(luaState, idx); ; int nombre = (int) number; // evil trick to determine wether n fits in an integer. (stolen from ltcl.c) if (number == nombre) { value = json_object_new_int((int) number); } else { value = json_object_new_double(number); } break; } case LUA_TBOOLEAN: value = json_object_new_boolean(lua_toboolean(luaState, idx)); break; case LUA_TSTRING: value = json_object_new_string(lua_tostring(luaState, idx)); break; case LUA_TTABLE: value = LuaTableToJson(source, luaState, idx); break; case LUA_TNIL: value = json_object_new_string("nil"); break; case LUA_TUSERDATA: value = json_object_new_int64((int64_t) lua_touserdata(luaState, idx)); // store userdata as int !!! break; default: AFB_API_NOTICE(source->api, "LuaPopOneArg: script returned Unknown/Unsupported idx=%d type:%d/%s", idx, luaType, lua_typename(luaState, luaType)); value = NULL; } return value; } static json_object *LuaPopArgs(CtlSourceT *source, lua_State* luaState, int start) { json_object *responseJ; int stop = lua_gettop(luaState); if (stop - start < 0) return NULL; // start at 2 because we are using a function array lib if (start == stop) { responseJ = LuaPopOneArg(source, luaState, start); } else { // loop on remaining return arguments responseJ = json_object_new_array(); for (int idx = start; idx <= stop; idx++) { json_object *argJ = LuaPopOneArg(source, luaState, idx); if (!argJ) return NULL; json_object_array_add(responseJ, argJ); } } return responseJ; } static int LuaFormatMessage(lua_State* luaState, int verbosity, int level) { char *message; CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) return 1; // if log level low then silently ignore message if (!afb_api_x3_wants_log_level(source->api, level)) return 0; json_object *responseJ = LuaPopArgs(source, luaState, LUA_FIRST_ARG + 1); if (!responseJ) { luaL_error(luaState, "LuaFormatMessage empty message"); return 1; } // if we have only on argument just return the value. if (json_object_get_type(responseJ) != json_type_array || json_object_array_length(responseJ) < 2) { message = (char*) json_object_get_string(responseJ); goto PrintMessage; } // extract format and push all other parameter on the stack message = alloca(LUA_MSG_MAX_LENGTH); const char *format = json_object_get_string(json_object_array_get_idx(responseJ, 0)); int arrayIdx = 1; int uidIdx = 0; for (int idx = 0; format[idx] != '\0'; idx++) { if (format[idx] == '%' && format[idx+1] != '\0') { json_object *slotJ = json_object_array_get_idx(responseJ, arrayIdx); //if (slotJ) AFB_NOTICE("**** idx=%d slotJ=%s", arrayIdx, json_object_get_string(slotJ)); switch (format[++idx]) { case 'd': if (slotJ) uidIdx += snprintf(&message[uidIdx], LUA_MSG_MAX_LENGTH - uidIdx, "%d", json_object_get_int(slotJ)); else uidIdx += snprintf(&message[uidIdx], LUA_MSG_MAX_LENGTH - uidIdx, "nil"); arrayIdx++; break; case 'f': if (slotJ) uidIdx += snprintf(&message[uidIdx], LUA_MSG_MAX_LENGTH - uidIdx, "%f", json_object_get_double(slotJ)); else uidIdx += snprintf(&message[uidIdx], LUA_MSG_MAX_LENGTH - uidIdx, "nil"); arrayIdx++; break; case'%': message[uidIdx] = '%'; uidIdx++; break; case 'A': uidIdx += snprintf(&message[uidIdx], LUA_MSG_MAX_LENGTH - uidIdx, "level: %s", source->uid); break; case 's': default: if (slotJ) uidIdx += snprintf(&message[uidIdx], LUA_MSG_MAX_LENGTH - uidIdx, "%s", json_object_get_string(slotJ)); else uidIdx += snprintf(&message[uidIdx], LUA_MSG_MAX_LENGTH - uidIdx, "nil"); arrayIdx++; } } else { if (uidIdx >= LUA_MSG_MAX_LENGTH) { const char *trunc = "... "; AFB_API_WARNING(source->api, "LuaFormatMessage: message[%s] overflow LUA_MSG_MAX_LENGTH=%d\n", format, LUA_MSG_MAX_LENGTH); uidIdx = LUA_MSG_MAX_LENGTH - 1; memcpy(&message[uidIdx - strlen(trunc)], trunc, strlen(trunc)); break; } else { message[uidIdx++] = format[idx]; } } } message[uidIdx] = '\0'; PrintMessage: // TBD: __file__ and __line__ should match LUA source code afb_api_verbose(source->api, level, __FILE__, __LINE__, source->uid, "%s", message); json_object_put(responseJ); return 0; // nothing return to lua } static int LuaPrintInfo(lua_State* luaState) { int err = LuaFormatMessage(luaState, AFB_VERBOSITY_LEVEL_INFO, AFB_SYSLOG_LEVEL_INFO); return err; } static int LuaPrintError(lua_State* luaState) { int err = LuaFormatMessage(luaState, AFB_VERBOSITY_LEVEL_ERROR, AFB_SYSLOG_LEVEL_ERROR); return err; // no value return } static int LuaPrintWarning(lua_State* luaState) { int err = LuaFormatMessage(luaState, AFB_VERBOSITY_LEVEL_WARNING, AFB_SYSLOG_LEVEL_WARNING); return err; } static int LuaPrintNotice(lua_State* luaState) { int err = LuaFormatMessage(luaState, AFB_VERBOSITY_LEVEL_NOTICE, AFB_SYSLOG_LEVEL_NOTICE); return err; } static int LuaPrintDebug(lua_State* luaState) { int err = LuaFormatMessage(luaState, AFB_VERBOSITY_LEVEL_DEBUG, AFB_SYSLOG_LEVEL_DEBUG); return err; } static int LuaAfbSuccess(lua_State* luaState) { CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_error(luaState); return 1; } // ignore context argument json_object *responseJ = LuaPopArgs(source, luaState, LUA_FIRST_ARG + 1); if (responseJ == JSON_ERROR) return 1; afb_req_success(source->request, responseJ, NULL); return 0; } static int LuaAfbFail(lua_State* luaState) { CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_error(luaState); return 1; } json_object *responseJ = LuaPopArgs(source, luaState, LUA_FIRST_ARG + 1); if (responseJ == JSON_ERROR) return 1; afb_req_success(source->request, json_object_new_string(source->uid), json_object_get_string(responseJ)); json_object_put(responseJ); return 0; } static void LuaAfbServiceCB(void *handle, int iserror, struct json_object *responseJ, afb_api_t apiHandle) { LuaCbHandleT *handleCb = (LuaCbHandleT*) handle; int count = 1; lua_getglobal(luaState, handleCb->callback); // Push AFB client context on the stack if (iserror) handleCb->source->status = CTL_STATUS_ERR; else handleCb->source->status = CTL_STATUS_DONE; LuaSourcePush(luaState, handleCb->source); // push response count += LuaPushArgument(handleCb->source, responseJ); if (handleCb->context) count += LuaPushArgument(handleCb->source, handleCb->context); int err = lua_pcall(luaState, count, LUA_MULTRET, 0); if (err) { AFB_API_ERROR(apiHandle, "LuaAfbServiceCB: Fail response=%s err=%s", json_object_to_json_string(responseJ), lua_tostring(luaState, -1)); } free(handleCb->source); free(handleCb); } static int LuaAfbService(lua_State* luaState) { int count = lua_gettop(luaState); CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaAfbService: Fail Invalid request handle"); lua_error(luaState); return 1; } // note: argument start at 2 because of AFB: table if (count < 6 || !lua_isstring(luaState, 3) || !lua_isstring(luaState, 4) || !lua_isstring(luaState, 6)) { lua_pushliteral(luaState, "LuaAfbService: syntax AFB:service(source, api, verb, {[Lua Table]})"); lua_error(luaState); return 1; } // get api/verb+query const char *api = lua_tostring(luaState, 3); const char *verb = lua_tostring(luaState, 4); json_object *queryJ = LuaPopOneArg(source, luaState, LUA_FIRST_ARG + 3); if (queryJ == JSON_ERROR) return 1; LuaCbHandleT *handleCb = calloc(1, sizeof (LuaCbHandleT)); handleCb->callback = lua_tostring(luaState, 6); handleCb->context = LuaPopArgs(source, luaState, 7); // source need to be duplicate because request return free it handleCb->source = malloc(sizeof (CtlSourceT)); handleCb->source = memcpy(handleCb->source, source, sizeof (CtlSourceT)); afb_api_call_legacy(source->api, api, verb, queryJ, LuaAfbServiceCB, handleCb); return 0; // no value return } static int LuaAfbServiceSync(lua_State* luaState) { int count = lua_gettop(luaState); json_object *responseJ; CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaAfbServiceSync: Fail Invalid request handle"); lua_error(luaState); return 1; } // note: argument start at 2 because of AFB: table if (count != 5 || ! lua_isstring(luaState, LUA_FIRST_ARG + 1) || ! lua_isstring(luaState, LUA_FIRST_ARG + 2)) { lua_pushliteral(luaState, "LuaAfbServiceSync: syntax AFB:servsync(source, api, verb, {[Lua Table]})"); lua_error(luaState); return 1; } // get source/api/verb+query const char *api = lua_tostring(luaState, LUA_FIRST_ARG + 1); const char *verb = lua_tostring(luaState, LUA_FIRST_ARG + 2); json_object *queryJ = LuaPopOneArg(source, luaState, LUA_FIRST_ARG + 3); if (queryJ == JSON_ERROR) return 1; int iserror = afb_api_call_sync_legacy(source->api, api, verb, queryJ, &responseJ); // push error status & response count = 1; lua_pushboolean(luaState, iserror); count += LuaPushArgument(source, responseJ); json_object_put(responseJ); return count; // return count values } static int LuaAfbEventPush(lua_State* luaState) { LuaAfbEvent *afbevt; CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaAfbMakePush: Fail Invalid request handle"); lua_error(luaState); return 1; } // if no private event handle then use default binding event if (!lua_islightuserdata(luaState, LUA_FIRST_ARG + 1)) { lua_pushliteral(luaState, "LuaAfbMakePush: Fail missing event handle"); lua_error(luaState); return 1; } afbevt = (LuaAfbEvent*) lua_touserdata(luaState, LUA_FIRST_ARG + 1); if (!afb_event_is_valid(afbevt->event)) { lua_pushliteral(luaState, "LuaAfbMakePush: Fail invalid event"); lua_error(luaState); return 1; } json_object *ctlEventJ = LuaTableToJson(source, luaState, LUA_FIRST_ARG + 2); if (!ctlEventJ) { lua_pushliteral(luaState, "LuaAfbEventPush: Syntax is AFB:signal ([evtHandle], {lua table})"); lua_error(luaState); return 1; } int done = afb_event_push(afbevt->event, ctlEventJ); if (!done) { lua_pushliteral(luaState, "LuaAfbEventPush: Fail No Subscriber to event"); AFB_API_ERROR(source->api, "LuaAfbEventPush: Fail name subscriber event=%s count=%d", afbevt->name, afbevt->count); lua_error(luaState); return 1; } afbevt->count++; return 0; } static int LuaAfbEventSubscribe(lua_State* luaState) { LuaAfbEvent *afbevt; CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaAfbEventSubscribe: Fail Invalid request handle"); lua_error(luaState); return 1; } // if no private event handle then use default binding event if (!lua_islightuserdata(luaState, LUA_FIRST_ARG + 1)) { lua_pushliteral(luaState, "LuaAfbEventSubscribe: Fail missing event handle"); lua_error(luaState); return 1; } afbevt = (LuaAfbEvent*) lua_touserdata(luaState, LUA_FIRST_ARG + 1); if (!afb_event_is_valid(afbevt->event)) { lua_pushliteral(luaState, "LuaAfbEventSubscribe: Fail invalid event handle"); lua_error(luaState); return 1; } int err = afb_req_subscribe(source->request, afbevt->event); if (err) { lua_pushliteral(luaState, "LuaAfbEventSubscribe: Fail No Subscriber to event"); AFB_API_ERROR(source->api, "LuaAfbEventSubscribe: Fail name subscriber event=%s count=%d", afbevt->name, afbevt->count); lua_error(luaState); return 1; } afbevt->count++; return 0; } static int LuaAfbEventUnsubscribe(lua_State* luaState) { LuaAfbEvent *afbevt; CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaAfbEventUnsubscribe: Fail Invalid request handle"); lua_error(luaState); return 1; } // if no private event handle then use default binding event if (!lua_islightuserdata(luaState, LUA_FIRST_ARG + 1)) { lua_pushliteral(luaState, "LuaAfbEventUnsubscribe: Fail missing event handle"); lua_error(luaState); return 1; } afbevt = (LuaAfbEvent*) lua_touserdata(luaState, LUA_FIRST_ARG + 1); if (!afb_event_is_valid(afbevt->event)) { lua_pushliteral(luaState, "LuaAfbEventUnsubscribe: Fail invalid event handle"); lua_error(luaState); return 1; } int err = afb_req_unsubscribe(source->request, afbevt->event); if (err) { lua_pushliteral(luaState, "LuaAfbEventUnsubscribe: Fail No Subscriber to event"); AFB_API_ERROR(source->api, "LuaAfbEventUnsubscribe: Fail name unsubscriber event=%s count=%d", afbevt->name, afbevt->count); lua_error(luaState); return 1; } afbevt->count++; return 0; } static int LuaLockWait(lua_State* luaState) { luaL_checktype(luaState, LUA_FIRST_ARG, LUA_TLIGHTUSERDATA); luaL_checktype(luaState, LUA_FIRST_ARG + 1, LUA_TNUMBER); CtlSourceT *source = (CtlSourceT *)LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaLockWait: Fail Invalid request handle"); lua_error(luaState); return 1; } uint64_t utimeout = (int)lua_tointeger(luaState, LUA_FIRST_ARG + 1); uint64_t remain_timeout = LockWait(source->api, utimeout); lua_pushinteger(luaState, remain_timeout); return 1; //return nb of pushed elements } static int LuaAfbEventMake(lua_State* luaState) { int count = lua_gettop(luaState); LuaAfbEvent *afbevt = calloc(1, sizeof (LuaAfbEvent)); CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaAfbEventMake: Fail Invalid request handle"); lua_error(luaState); return 1; } if (count != LUA_FIRST_ARG + 1 || !lua_isstring(luaState, LUA_FIRST_ARG + 1)) { lua_pushliteral(luaState, "LuaAfbEventMake: Syntax is evtHandle= AFB:event ('myEventName')"); lua_error(luaState); return 1; } // event name should be the only argument afbevt->name = strdupa(lua_tostring(luaState, LUA_FIRST_ARG + 1)); // create a new binder event afbevt->event = afb_api_make_event(source->api, afbevt->name); if (!afb_event_is_valid(afbevt->event)) { AFB_API_ERROR(source->api, "Fail to CreateEvent evtname=%s", afbevt->name); lua_pushliteral(luaState, "LuaAfbEventMake: Fail to Create Binder event"); lua_error(luaState); return 1; } // push event handler as a LUA opaque handle lua_pushlightuserdata(luaState, afbevt); return 1; } static int LuaAfbGetApiName(lua_State* luaState) { CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaAfbGetApiName: Fail Invalid request handle"); return 0; } // extract and return afbSource from timer handle lua_pushstring(luaState, source->api->apiname); return 1; // return argument } static int LuaAfbGetUid(lua_State* luaState) { CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaAfbGetUid: Fail Invalid request handle"); return 0; } // extract and return afbSource from timer handle lua_pushstring(luaState, source->uid); return 1; // return argument } static int LuaAfbGetRootDir(lua_State* luaState) { CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaAfbGetRootDir: Fail Invalid request handle"); return 0; } // extract and return afbSource from timer handle lua_pushstring(luaState, GetBindingDirPath(source->api)); return 1; // return argument } static int LuaAfbGetStatus(lua_State* luaState) { CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_pushliteral(luaState, "LuaAfbGetStatus: Fail Invalid request handle"); return 0; } // extract and return afbSource from timer handle lua_pushinteger(luaState, source->status); return 1; // return argument } // Function call from LUA when lua2c plugin L2C is used int Lua2cWrapper(void* luaHandle, char *funcname, Lua2cFunctionT callback) { lua_State* luaState = (lua_State*) luaHandle; json_object *responseJ = NULL; CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); json_object *argsJ = LuaPopArgs(source, luaState, LUA_FIRST_ARG + 1); int err = (*callback) (source, argsJ, &responseJ); json_object_put(argsJ); // push error code and eventual response to LUA int count = 1; lua_pushinteger(luaState, err); if (responseJ) count += LuaPushArgument(source, responseJ); return count; } // Call a Lua function from a control action int LuaCallFunc(CtlSourceT *source, CtlActionT *action, json_object *queryJ) { int err = 0, count = 1; json_object* argsJ = action->argsJ; const char* func = action->exec.lua.funcname; // load function (should exist in CONTROL_PATH_LUA) lua_getglobal(luaState, func); // push source on the stack // Push AFB client context on the stack LuaAfbSourceT *afbSource = LuaSourcePush(luaState, source); if (!afbSource) return -1; // push argsJ on the stack if (!argsJ) { lua_pushnil(luaState); count++; } else { count += LuaPushArgument(source, argsJ); } // push queryJ on the stack if (!queryJ) { lua_pushnil(luaState); count++; } else { count += LuaPushArgument(source, queryJ); } // effectively exec LUA script code err = lua_pcall(luaState, count, 1, 0); if (err) { AFB_API_ERROR(action->api, "LuaCallFunc: Fail calling %s error=%s", func, lua_tostring(luaState, -1)); return -1; } // return LUA script value int rc = (int) lua_tointeger(luaState, -1); return rc; } int LuaLoadScript(afb_api_t apiHandle, const char *luaScriptPath) { int err = 0; if (!luaScriptPath) { AFB_API_ERROR(apiHandle, "Error: provided path is NULL"); return -1; } err = luaL_loadfile(luaState, luaScriptPath); if (err) { AFB_API_ERROR(apiHandle, "Error at load for %s: %s", luaScriptPath, lua_tostring(luaState, -1)); return err; } // Script was loaded we need to parse to make it executable err = lua_pcall(luaState, 0, 0, 0); if (err) { AFB_API_ERROR(apiHandle, "Error at execution for %s: %s", luaScriptPath, lua_tostring(luaState, -1)); return err; } return err; } static int LuaDoScript(json_object *queryJ, CtlSourceT *source) { const char *uid = NULL, *func = NULL, *filename = NULL, *fullpath = NULL; char *luaScriptPath = NULL; int index, err = 0; size_t p_length = 0; json_object *argsJ = NULL; static json_object *luaScriptPathJ = NULL; if (!queryJ) { return -1; } err = wrap_json_unpack(queryJ, "{s:s,s?s,ss,s?o !}", "uid", &uid, "spath", &luaScriptPathJ, "action", &func, "args", &argsJ); if (err) { AFB_API_ERROR(source->api, "LUA-DOSCRIPT-SCAN: Miss something in JSON object uid|[spath]|action|[args]: %s", json_object_to_json_string(queryJ)); return -1; } // search for filename=uid*.lua or fallback on bindername*.lua in current directory if (!luaScriptPathJ) { luaScriptPathJ = ScanForConfig(".", CTL_SCAN_RECURSIVE, uid, ".lua"); if (!luaScriptPathJ) luaScriptPathJ = ScanForConfig(".", CTL_SCAN_RECURSIVE, GetBinderName(), ".lua"); } if(!luaScriptPathJ) { AFB_API_ERROR(source->api, "LUA-DOSCRIPT-SCAN: No script found"); return -1; } for (index = 0; index < json_object_array_length(luaScriptPathJ); index++) { json_object *entryJ = json_object_array_get_idx(luaScriptPathJ, index); err = wrap_json_unpack(entryJ, "{s:s, s:s !}", "fullpath", &fullpath, "filename", &filename); if (err) { AFB_API_ERROR(source->api, "LUA-DOSCRIPT-SCAN:HOOPs invalid config file path = %s", json_object_to_json_string(entryJ)); return -1; } // Ignoring other found script. Only take the first one. if (!index) { p_length = strlen(fullpath) + 1 + strlen(filename); luaScriptPath = malloc(p_length + 1); strncpy(luaScriptPath, fullpath, CONTROL_MAXPATH_LEN - 1); strncat(luaScriptPath, "/", CONTROL_MAXPATH_LEN - strlen(luaScriptPath) - 1); strncat(luaScriptPath, filename, CONTROL_MAXPATH_LEN - strlen(luaScriptPath) - 1); } } err = LuaLoadScript(source->api, luaScriptPath); if (err) return -1; // load function (should exist in CONTROL_PATH_LUA lua_getglobal(luaState, func); // Push AFB client context on the stack LuaAfbSourceT *afbSource = LuaSourcePush(luaState, source); if (!afbSource) return -1; return 0; } static int LuaDoCall(json_object *queryJ, CtlSourceT *source) { int err = 0; int count = 0; const char *func; json_object *argsJ = NULL; if (!queryJ) return -1; err = wrap_json_unpack(queryJ, "{s:s, s?o !}", "uid", &func, "args", &argsJ); if (err) return -2; // load function (should exist in CONTROL_PATH_LUA lua_getglobal(luaState, func); // Push AFB client context on the stack LuaAfbSourceT *afbSource = LuaSourcePush(luaState, source); if (!afbSource) return -3; // push query on the stack if (!argsJ) { lua_pushnil(luaState); count++; } else { count += LuaPushArgument(source, argsJ); } return count; } static int LuaDoString(const char *script, CtlSourceT *source) { int err = 0; err = luaL_loadstring(luaState, script); if (err) return -1; // Push AFB client context on the stack if (source) { LuaAfbSourceT *afbSource = LuaSourcePush(luaState, source); if (!afbSource) return -2; } return 0; } // Execute LUA code from received API request static void LuaDoAction(LuaDoActionT action, afb_req_t request) { int err = 0, count = 0; CtlSourceT *source = alloca(sizeof (CtlSourceT)); source->request = request; json_object* queryJ = afb_req_json(request); switch (action) { case LUA_DOSTRING: { const char *script = json_object_get_string(queryJ); count = LuaDoString(script, source); if (count) AFB_API_ERROR(source->api, "DOSTRING goes wrong err=%d, String=%s ", count, script); break; } case LUA_DOCALL: { count = LuaDoCall(queryJ, source); if (count) AFB_API_ERROR(source->api, "DOCALL goes wrong, error = %d, query=%s", count, json_object_get_string(queryJ)); break; } case LUA_DOSCRIPT: { // Fulup need to fix argument passing count = LuaDoScript(queryJ, source); if (count) AFB_API_ERROR(source->api, "DOSCRIPT goes wrong error=%d query=%s", count, json_object_to_json_string(queryJ)); break; } default: AFB_API_ERROR(source->api, "LUA-DOSCRIPT-ACTION unknown query=%s", json_object_to_json_string(queryJ)); afb_req_success(request, json_object_new_string("LUA:ERROR"), lua_tostring(luaState, -1)); return; } if (count >= 0) err = lua_pcall(luaState, count + 1, 0, 0); if (err) { AFB_API_ERROR(source->api, "LUA-DO-EXEC:FAIL query=%s err=%s", json_object_to_json_string(queryJ), lua_tostring(luaState, -1)); afb_req_success(request, json_object_new_string("LUA:ERROR"), lua_tostring(luaState, -1)); return; } return; } void ctlapi_execlua(afb_req_t request) { LuaDoAction(LUA_DOSTRING, request); } void ctlapi_request(afb_req_t request) { LuaDoAction(LUA_DOCALL, request); } void ctlapi_debuglua(afb_req_t request) { LuaDoAction(LUA_DOSCRIPT, request); } static TimerHandleT *LuaTimerPop(lua_State *luaState, int index) { TimerHandleT *timerHandle; luaL_checktype(luaState, index, LUA_TLIGHTUSERDATA); timerHandle = (TimerHandleT *) lua_touserdata(luaState, index); if (timerHandle == NULL) { luaL_error(luaState, "Invalid source handle"); fprintf(stderr, "LuaSourcePop error retrieving afbSource"); return NULL; } return timerHandle; } static int LuaTimerClear(lua_State* luaState) { // retrieve useful information opaque handle TimerHandleT *timerHandle = LuaTimerPop(luaState, LUA_FIRST_ARG); if (!timerHandle) return 1; LuaCbHandleT *luaCbHandle = (LuaCbHandleT*) timerHandle->context; AFB_API_NOTICE(luaCbHandle->source->api, "LuaTimerClear timer=%s", timerHandle->uid); TimerEvtStop(timerHandle); return 0; //happy end } static int LuaTimerGet(lua_State* luaState) { // retrieve useful information opaque handle TimerHandleT *timerHandle = LuaTimerPop(luaState, LUA_FIRST_ARG); if (!timerHandle) return 0; LuaCbHandleT *luaCbHandle = (LuaCbHandleT*) timerHandle->context; // create response as a JSON object json_object *responseJ = json_object_new_object(); json_object_object_add(responseJ, "uid", json_object_new_string(timerHandle->uid)); json_object_object_add(responseJ, "delay", json_object_new_int(timerHandle->delay)); json_object_object_add(responseJ, "count", json_object_new_int(timerHandle->count)); // return JSON object as Lua table int count = LuaPushArgument(luaCbHandle->source, responseJ); // free json object json_object_put(responseJ); return count; // return argument } // Timer Callback // Set timer static int LuaTimerSetCB(TimerHandleT *timer) { LuaCbHandleT *LuaCbHandle = (LuaCbHandleT*) timer->context; int count; // push timer handle and user context on Lua stack lua_getglobal(luaState, LuaCbHandle->callback); // Push AFB client context on the stack count = 1; LuaAfbSourceT *afbSource = LuaSourcePush(luaState, LuaCbHandle->source); if (!afbSource) return 1; // Push AFB client context on the stack count++; lua_pushlightuserdata(luaState, timer); if (!afbSource) return 1; // Push user Context count += LuaPushArgument(LuaCbHandle->source, LuaCbHandle->context); int err = lua_pcall(luaState, count, LUA_MULTRET, 0); if (err) { AFB_API_ERROR(LuaCbHandle->source->api, "LUA-TIMER-CB:FAIL response=%s err=%s", json_object_to_json_string(LuaCbHandle->context), lua_tostring(luaState, -1)); return 1; } // get return parameter if (!lua_isboolean(luaState, -1)) { return (lua_toboolean(luaState, -1)); } return 0; // By default we are happy } // Free Timer context handle static int LuaTimerCtxFree(void *handle) { LuaCbHandleT *LuaCbHandle = (LuaCbHandleT*) handle; free(LuaCbHandle->source); free(LuaCbHandle); return 0; } static int LuaTimerSet(lua_State* luaState) { const char *uid = NULL, *info = NULL; int delay = 0, count = 0; // Get source handle CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_error(luaState); return 1; // return error code } json_object *timerJ = LuaPopOneArg(source, luaState, LUA_FIRST_ARG + 1); const char *callback = lua_tostring(luaState, LUA_FIRST_ARG + 2); json_object *contextJ = LuaPopOneArg(source, luaState, LUA_FIRST_ARG + 3); if (lua_gettop(luaState) != LUA_FIRST_ARG + 3 || !timerJ || !callback || !contextJ) { lua_pushliteral(luaState, "LuaTimerSet: Syntax timerset (source, timerT, 'callback', contextT)"); lua_error(luaState); return 1; // return error code } int err = wrap_json_unpack(timerJ, "{ss, s?s si, si !}", "uid", &uid, "info", &info, "delay", &delay, "count", &count); if (err) { lua_pushliteral(luaState, "LuaTimerSet: Syntax timerT={uid:xxx delay:ms, count:xx}"); lua_error(luaState); return 1; // return error code } // Allocate handle to store context and callback LuaCbHandleT *handleCb = calloc(1, sizeof (LuaCbHandleT)); handleCb->callback = callback; handleCb->context = contextJ; handleCb->source = malloc(sizeof (CtlSourceT)); memcpy(handleCb->source, source, sizeof (CtlSourceT)); // Fulup need to be free when timer is done // everything look fine create timer structure TimerHandleT *timerHandle = malloc(sizeof (TimerHandleT)); timerHandle->delay = delay; timerHandle->count = count; timerHandle->uid = uid; timerHandle->freeCB = LuaTimerCtxFree; // fire timer TimerEvtStart(source->api, timerHandle, LuaTimerSetCB, handleCb); // Fulup finir les timers avec handle // return empty error code plus timer handle lua_pushnil(luaState); lua_pushlightuserdata(luaState, timerHandle); return 2; } typedef struct { const char *callback; json_object *clientCtxJ; CtlSourceT *source; } LuaClientCtxT; static void *LuaClientCtxNew(void * handle) { LuaClientCtxT *clientCtx = (LuaClientCtxT*) handle; int count = 1; // push callback and client context on Lua stack lua_getglobal(luaState, clientCtx->callback); // let's Lua script know about new/free clientCtx->source->status = CTL_STATUS_DONE; // Push AFB client context on the stack LuaAfbSourceT *afbSource = LuaSourcePush(luaState, clientCtx->source); if (!afbSource) return NULL; // Push user Context count += LuaPushArgument(clientCtx->source, clientCtx->clientCtxJ); int err = lua_pcall(luaState, count, 1, 0); if (err) { AFB_API_ERROR(clientCtx->source->api, "LuaClientCtxNew:FAIL response=%s err=%s", json_object_to_json_string(clientCtx->clientCtxJ), lua_tostring(luaState, -1)); return NULL; } // If function return an error status then free client context if (lua_tointeger(luaState, -1)) { free(clientCtx); return NULL; } return handle; // By default we are happy } static void LuaClientCtxFree(void * handle) { LuaClientCtxT *clientCtx = (LuaClientCtxT*) handle; int count = 1; if (!handle) return; // let's Lua script know about new/free lua_getglobal(luaState, clientCtx->callback); // set source status to notify lua script about free clientCtx->source->status = CTL_STATUS_FREE; // Push AFB client context on the stack LuaAfbSourceT *afbSource = LuaSourcePush(luaState, clientCtx->source); if (!afbSource) return; // Push user Context count += LuaPushArgument(clientCtx->source, clientCtx->clientCtxJ); int err = lua_pcall(luaState, count, LUA_MULTRET, 0); if (err) { AFB_API_ERROR(clientCtx->source->api, "LuaClientCtxFree:FAIL response=%s err=%s", json_object_to_json_string(clientCtx->clientCtxJ), lua_tostring(luaState, -1)); return; } // If function return an error status then free client context if (lua_toboolean(luaState, -1)) { free(clientCtx); return; } return; // No return status } // set a context that will be free when WS is closed static int LuaClientCtx(lua_State* luaState) { // Get source handle CtlSourceT *source = LuaSourcePop(luaState, LUA_FIRST_ARG); if (!source) { lua_error(luaState); return 1; // return error code } if (!afb_req_is_valid(source->request)) { lua_pushliteral(luaState, "LuaSessionSet-Syntax should be called within client request context"); lua_error(luaState); return 1; // return error code } // in only one arg then we should free the clientCtx if (lua_gettop(luaState) == LUA_FIRST_ARG) { afb_req_context_clear(source->request); lua_pushnil(luaState); return 1; } const char *callback = lua_tostring(luaState, LUA_FIRST_ARG + 1); json_object *clientCtxJ = LuaPopOneArg(source, luaState, LUA_FIRST_ARG + 2); if (lua_gettop(luaState) != LUA_FIRST_ARG + 2 || !clientCtxJ || !callback) { lua_pushliteral(luaState, "LuaClientCtx-Syntax clientCtx (source, callback, clientCtx)"); lua_error(luaState); return 1; // return error code } // Allocate handle to store clientCtx and callback LuaClientCtxT *clientCtx = calloc(1, sizeof (LuaCbHandleT)); clientCtx->callback = callback; clientCtx->clientCtxJ = clientCtxJ; clientCtx->source = malloc(sizeof (CtlSourceT)); memcpy(clientCtx->source, source, sizeof (CtlSourceT)); // source if free when command return // push client context within session void *done = afb_req_context(source->request, 1, LuaClientCtxNew, LuaClientCtxFree, clientCtx); if (!done) { lua_pushliteral(luaState, "LuaClientCtx-Fail to allocate client context)"); lua_error(luaState); return 1; // return error code } return 1; } // Register a new L2c list of LUA user plugin commands void LuaL2cNewLib(luaL_Reg *l2cFunc, int count, const char *prefix) { // luaL_newlib(luaState, l2cFunc); macro does not work with pointer :( luaL_checkversion(luaState); lua_createtable(luaState, 0, count + 1); luaL_setfuncs(luaState, l2cFunc, 0); lua_setglobal(luaState, prefix); } static const luaL_Reg afbFunction[] = { {"timerclear", LuaTimerClear}, {"timerget", LuaTimerGet}, {"timerset", LuaTimerSet}, {"notice", LuaPrintNotice}, {"info", LuaPrintInfo}, {"warning", LuaPrintWarning}, {"debug", LuaPrintDebug}, {"error", LuaPrintError}, {"servsync", LuaAfbServiceSync}, {"service", LuaAfbService}, {"success", LuaAfbSuccess}, {"fail", LuaAfbFail}, {"subscribe", LuaAfbEventSubscribe}, {"unsubscribe", LuaAfbEventUnsubscribe}, {"evtmake", LuaAfbEventMake}, {"evtpush", LuaAfbEventPush}, {"getapiname", LuaAfbGetApiName}, {"getuid", LuaAfbGetUid}, {"getrootdir", LuaAfbGetRootDir}, {"status", LuaAfbGetStatus}, {"context", LuaClientCtx}, {"lockwait", LuaLockWait}, {NULL, NULL} /* sentinel */ }; // Load Lua Interpreter int LuaConfigExec(afb_api_t apiHandle) { int err; // Load LUA utils functions. err = LuaDoString(lua_utils, NULL); if (err) { AFB_API_ERROR(apiHandle, "Error loading lua_utils default functions.%s, %d", lua_utils, err); return -1; } err = lua_pcall(luaState, 0, 0, 0); if (err) { AFB_API_ERROR(apiHandle, "Error loading lua_utils default functions at pcall.%s, %d", lua_tostring(luaState, 0), err); return -1; } return 0; } // Load Lua Interpreter int LuaConfigLoad(afb_api_t apiHandle, const char *prefix) { size_t total_len = 0, base_len = 0, spath_len = 0; static int luaLoaded = 0; int token_nb = 0, i = 0; char *spath = NULL, *sep = NULL, *lua_str = NULL; // Lua loads only once if (luaLoaded) return 0; luaLoaded = 1; // open a new LUA interpretor luaState = luaL_newstate(); if (!luaState) { AFB_API_ERROR(apiHandle, "LUA_INIT: Fail to open lua interpretor"); free(luaState); return 1; } // load auxiliary libraries luaL_openlibs(luaState); // redirect print to AFB_NOTICE luaL_newlib(luaState, afbFunction); lua_setglobal(luaState, "AFB"); // set package.path lua variable use the CONTROL_PLUGIN_PATH as it could // have to find external lua packages in those directories spath = GetDefaultPluginSearchPath(apiHandle, prefix); base_len = strlen(LUA_PATH_VALUE); spath_len = strlen(spath); token_nb = spath_len ? 1:0; sep = spath; while((sep = strchr(sep, ':')) != NULL) { token_nb++; sep++; } /* allocate 2 extra bytes for the ending single quote + NULL char */ total_len = base_len + spath_len + token_nb * strlen(LUA_GLOB_PATTERN) + 2; lua_str = malloc(total_len); strncpy(lua_str, LUA_PATH_VALUE, total_len); for (i = 0; i < token_nb; i++) { sep = strsep(&spath, ":"); strncat(lua_str, sep, total_len - strlen(lua_str) - 1); strncat(lua_str, LUA_GLOB_PATTERN, total_len - strlen(lua_str) -1); } strncat(lua_str, "'", total_len - strlen(lua_str) - 1); if(luaL_dostring(luaState, lua_str)) printf("Fail change package.path error=%s", lua_tostring(luaState, -1)); free(spath); free(lua_str); // initialise static magic for context #ifndef CTX_MAGIC_VALUE CTX_MAGIC = CtlConfigMagicNew(); #endif return 0; }