/* * Copyright (C) 2016 "IoT.bzh" * Author Fulup Ar Foll <fulup@iot.bzh> * * 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: * http://www.troubleshooters.com/codecorn/lua/lua_c_calls_lua.htm#_Anatomy_of_a_Lua_Call * http://acamara.es/blog/2012/08/passing-variables-from-lua-5-2-to-c-and-vice-versa/ */ #define _GNU_SOURCE #include <stdio.h> #include <string.h> #include <dirent.h> #include "lua.h" #include "lauxlib.h" #include "lualib.h" #include "ctl-binding.h" #include "wrap-json.h" static lua_State* luaState; typedef enum { LUA_DOCALL, LUA_DOSTRING, LUA_DOSCRIPT, } LuaDoActionT; // List Avaliable Configuration Files PUBLIC json_object* ScanForConfig (char* searchPath, char *pre, char *ext) { json_object *responseJ; DIR *dirHandle; char *dirPath; char* dirList= strdup(searchPath); size_t extLen = strlen(ext); responseJ = json_object_new_array(); for (dirPath= strtok(dirList, ":"); dirPath && *dirPath; dirPath=strtok(NULL,":")) { struct dirent *dirEnt; dirHandle = opendir (dirPath); if (!dirHandle) { AFB_NOTICE ("CONFIG-SCANNING dir=%s not readable", dirPath); continue; } AFB_NOTICE ("CONFIG-SCANNING:ctl_listconfig scanning: %s", dirPath); while ((dirEnt = readdir(dirHandle)) != NULL) { // Unknown type is accepted to support dump filesystems if (dirEnt->d_type == DT_REG || dirEnt->d_type == DT_UNKNOWN) { // check prefix and extention size_t extIdx=strlen(dirEnt->d_name) - extLen; if (extIdx <= 0) continue; if (pre && !strcasestr (dirEnt->d_name, pre)) continue; if (ext && strcasecmp (ext, &dirEnt->d_name[extIdx])) continue; struct json_object *pathJ = json_object_new_object(); json_object_object_add(pathJ, "dirpath", json_object_new_string(dirPath)); json_object_object_add(pathJ, "filename", json_object_new_string(dirEnt->d_name)); json_object_array_add(responseJ, pathJ); } } } free (dirList); return (responseJ); } STATIC int LuaPrintNotice(lua_State* luaState) { int count = lua_gettop(luaState); for (int idx=1; idx <= count; ++idx) { const char *str = lua_tostring(luaState, idx); // Get string // Output string. AFB_NOTICE (str); } return 0; // no value return } STATIC int LuaPushArgument (json_object *arg) { switch (json_object_get_type(arg)) { case json_type_object: lua_newtable (luaState); json_object_object_foreach (arg, key, val) { int done = LuaPushArgument (val); if (done) { lua_pushstring(luaState, key); // table.key = val lua_settable(luaState, -3); } } break; case json_type_int: lua_pushinteger(luaState, json_object_get_int(arg)); break; case json_type_string: lua_pushstring(luaState, json_object_get_string(arg)); break; case json_type_boolean: lua_pushboolean(luaState, json_object_get_boolean(arg)); break; case json_type_double: lua_pushnumber(luaState, json_object_get_double(arg)); break; default: return 0; } return 1; } // Generated some fake event based on watchdog/counter PUBLIC void LuaDoAction (LuaDoActionT action, afb_req request) { int err, count=0; json_object* queryJ = afb_req_json(request); switch (action) { case LUA_DOSTRING: { const char *script = json_object_get_string(queryJ); err=luaL_loadstring(luaState, script); if (err) { AFB_ERROR ("LUA-DO-COMPILE:FAIL String=%s err=%s", script, lua_tostring(luaState,-1) ); goto OnErrorExit; } break; } case LUA_DOCALL: { const char *func; json_object *args; err= wrap_json_unpack (queryJ, "{s:s, s?o !}", "func", &func,"args", &args); if (err) { AFB_ERROR ("LUA-DOCALL-SYNTAX missing func|args args=%s", json_object_get_string(queryJ)); goto OnErrorExit; } // load function (should exist in CONTROL_PATH_LUA lua_getglobal(luaState, func); // push arguments on the stack if (json_object_get_type(args) != json_type_array) { count= LuaPushArgument (args); } else { for (int idx=0; idx<json_object_array_length(args); idx++) { count += LuaPushArgument (json_object_array_get_idx(args, idx)); if (err) break; } } break; } case LUA_DOSCRIPT: { const char *script; json_object *args; int index; // scan luascript search path once static json_object *luaScriptPathJ =NULL; if (!luaScriptPathJ) luaScriptPathJ= ScanForConfig(CONTROL_PATH_LUA , NULL, "lua"); err= wrap_json_unpack (queryJ, "{s:s, s?o s?o !}", "script", &script,"args", &args, "arg", &args); if (err) { AFB_ERROR ("LUA-DOCALL-SYNTAX missing script|(args,arg) args=%s", json_object_get_string(queryJ)); goto OnErrorExit; } // push arguments on the stack if (json_object_get_type(args) != json_type_array) { lua_createtable(luaState, 1, 0); count= LuaPushArgument (args); } else { int length= json_object_array_length(args); lua_createtable(luaState, length, 0); for (int idx=0; idx < length; idx++) { count += LuaPushArgument (json_object_array_get_idx(args, idx)); if (err) break; } } for (index=0; index < json_object_array_length(luaScriptPathJ); index++) { char *filename; char*dirpath; json_object *entryJ=json_object_array_get_idx(luaScriptPathJ, index); err= wrap_json_unpack (entryJ, "{s:s, s:s !}", "dirpath", &dirpath,"filename", &filename); if (err) { AFB_ERROR ("LUA-DOSCRIPT HOOPs invalid LUA script path = %s", json_object_get_string(entryJ)); goto OnErrorExit; } if (strcmp(filename, script)) continue; char filepath[255]; strncpy(filepath, dirpath, sizeof(filepath)); strncat(filepath, "/", sizeof(filepath)); strncat(filepath, filename, sizeof(filepath)); err= luaL_loadfile(luaState, filepath); if (err) { AFB_ERROR ("LUA-DOSCRIPT HOOPs Error in LUA loading scripts=%s err=%s", filepath, lua_tostring(luaState,-1)); goto OnErrorExit; } break; } if (index == json_object_array_length(luaScriptPathJ)) { AFB_ERROR ("LUA-DOSCRIPT HOOPs script=%s not in path=%s", script, CONTROL_PATH_LUA); goto OnErrorExit; } //lua_setglobal(luaState, "args"); count=1; break; } default: AFB_ERROR ("LUA-DOCALL-ACTION unknown query=%s", json_object_get_string(queryJ)); goto OnErrorExit; } // effectively exec LUA code // err=lua_pcall(luaState, count, LUA_MULTRET, 0); err=lua_pcall(luaState, count, LUA_MULTRET, 0); if (err) { AFB_ERROR ("LUA-DO-EXEC:FAIL query=%s err=%s", json_object_get_string(queryJ), lua_tostring(luaState,-1) ); goto OnErrorExit; } // number of return values count=lua_gettop(luaState); // Check status return true=OK false=fail if (count == 0 || !lua_isboolean(luaState, 1)) { AFB_ERROR ("LUA-DO-RETURN:INVALID 1st return argument not boolean query=%s", json_object_get_string(queryJ) ); goto OnErrorExit; } // get return fail/ok status const int returnStatus=lua_toboolean(luaState, 1); // loop on remaining return arguments json_object *returnArgs = json_object_new_array(); for (int idx=2; idx <= count; idx++) { // push on stack return value (Fulup in LUA-5.3 the two functions are combined) int valueType=lua_type (luaState, idx); switch(valueType) { case LUA_TNUMBER: json_object_array_add(returnArgs, json_object_new_double(lua_tonumber(luaState, idx))); break; case LUA_TBOOLEAN: json_object_array_add(returnArgs, json_object_new_boolean(lua_toboolean(luaState, idx))); break; case LUA_TSTRING: json_object_array_add(returnArgs, json_object_new_string(lua_tostring(luaState, idx))); break; case LUA_TTABLE: default: AFB_NOTICE ("script returned Unknown/Unsupported type: "); } } // pop stack including Func/String/Script call lua_pop(luaState, count+1); if(returnStatus) afb_req_success(request, returnArgs, NULL); else afb_req_fail(request, "LUA-DOACTION-FAIL", json_object_get_string(returnArgs)); return; OnErrorExit: afb_req_fail(request,"LUA:ERROR", lua_tostring(luaState,-1)); return; } PUBLIC void ctlapi_lua_dostring (afb_req request) { LuaDoAction (LUA_DOSTRING, request); } PUBLIC void ctlapi_lua_docall (afb_req request) { LuaDoAction (LUA_DOCALL, request); } PUBLIC void ctlapi_lua_doscript (afb_req request) { LuaDoAction (LUA_DOSCRIPT, request); } // Create Binding Event at Init PUBLIC int LuaLibInit () { int err, index; // search for default policy config file json_object *luaScriptPathJ = ScanForConfig(CONTROL_PATH_LUA , "onload", "lua"); // open a new LUA interpretor luaState = luaL_newstate(); if (!luaState) { AFB_ERROR ("LUA_INIT: Fail to open lua interpretor"); goto OnErrorExit; } // load auxiliary libraries luaL_openlibs(luaState); // redirect print to AFB_NOTICE lua_register(luaState,"print", LuaPrintNotice ); //lua_register(lauHandle,"AFB_DEBUG", LuaAFBDebug); // load+exec any file found in LUA search path for (index=0; index < json_object_array_length(luaScriptPathJ); index++) { json_object *entryJ=json_object_array_get_idx(luaScriptPathJ, index); char *filename; char*dirpath; err= wrap_json_unpack (entryJ, "{s:s, s:s !}", "dirpath", &dirpath,"filename", &filename); if (err) { AFB_ERROR ("LUA-INIT HOOPs invalid config file path = %s", json_object_get_string(entryJ)); goto OnErrorExit; } char filepath[255]; strncpy(filepath, dirpath, sizeof(filepath)); strncat(filepath, "/", sizeof(filepath)); strncat(filepath, filename, sizeof(filepath)); err= luaL_loadfile(luaState, filepath); if (err) { AFB_ERROR ("LUA-LOAD HOOPs Error in LUA loading scripts=%s err=%s", filepath, lua_tostring(luaState,-1)); goto OnErrorExit; } // exec/compil script err = lua_pcall(luaState, 0, 0, 0); if (err) { AFB_ERROR ("LUA-LOAD HOOPs Error in LUA exec scripts=%s err=%s", filepath, lua_tostring(luaState,-1)); goto OnErrorExit; } } // no policy config found remove control API from binder if (index == 0) { AFB_WARNING ("POLICY-INIT:WARNING No Control LUA file in path=[%s]", CONTROL_PATH_LUA); } AFB_DEBUG ("Audio control-LUA Init Done"); return 0; OnErrorExit: return -1; }