/** * collectd - src/lua.c * Copyright (C) 2010 Julien Ammous * Copyright (C) 2010 Florian Forster * Copyright (C) 2016 Ruben Kerkhof * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Authors: * Julien Ammous * Florian Forster * Ruben Kerkhof **/ #include "collectd.h" #include "plugin.h" #include "utils/common/common.h" #include "utils_lua.h" /* Include the Lua API header files. */ #include #include #include #include #define PLUGIN_READ 1 #define PLUGIN_WRITE 2 typedef struct lua_script_s { lua_State *lua_state; struct lua_script_s *next; } lua_script_t; typedef struct { lua_State *lua_state; char *lua_function_name; pthread_mutex_t lock; int callback_id; } clua_callback_data_t; static char base_path[PATH_MAX]; static lua_script_t *scripts; static int clua_store_callback(lua_State *L, int idx) /* {{{ */ { /* Copy the function pointer */ lua_pushvalue(L, idx); return luaL_ref(L, LUA_REGISTRYINDEX); } /* }}} int clua_store_callback */ static int clua_load_callback(lua_State *L, int callback_ref) /* {{{ */ { lua_rawgeti(L, LUA_REGISTRYINDEX, callback_ref); if (!lua_isfunction(L, -1)) { lua_pop(L, 1); return -1; } return 0; } /* }}} int clua_load_callback */ /* Store the threads in a global variable so they are not cleaned up by the * garbage collector. */ static int clua_store_thread(lua_State *L, int idx) /* {{{ */ { if (!lua_isthread(L, idx)) { return -1; } /* Copy the thread pointer */ lua_pushvalue(L, idx); luaL_ref(L, LUA_REGISTRYINDEX); return 0; } /* }}} int clua_store_thread */ static int clua_read(user_data_t *ud) /* {{{ */ { clua_callback_data_t *cb = ud->data; pthread_mutex_lock(&cb->lock); lua_State *L = cb->lua_state; int status = clua_load_callback(L, cb->callback_id); if (status != 0) { ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).", cb->lua_function_name, cb->callback_id); pthread_mutex_unlock(&cb->lock); return -1; } /* +1 = 1 */ status = lua_pcall(L, 0, 1, 0); if (status != 0) { const char *errmsg = lua_tostring(L, -1); if (errmsg == NULL) ERROR("Lua plugin: Calling a read callback failed. " "In addition, retrieving the error message failed."); else ERROR("Lua plugin: Calling a read callback failed: %s", errmsg); lua_pop(L, 1); pthread_mutex_unlock(&cb->lock); return -1; } if (!lua_isnumber(L, -1)) { ERROR("Lua plugin: Read function \"%s\" (id %i) did not return a numeric " "status.", cb->lua_function_name, cb->callback_id); status = -1; } else { status = (int)lua_tointeger(L, -1); } /* pop return value and function */ lua_pop(L, 1); /* -1 = 0 */ pthread_mutex_unlock(&cb->lock); return status; } /* }}} int clua_read */ static int clua_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */ user_data_t *ud) { clua_callback_data_t *cb = ud->data; pthread_mutex_lock(&cb->lock); lua_State *L = cb->lua_state; int status = clua_load_callback(L, cb->callback_id); if (status != 0) { ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).", cb->lua_function_name, cb->callback_id); pthread_mutex_unlock(&cb->lock); return -1; } /* +1 = 1 */ status = luaC_pushvaluelist(L, ds, vl); if (status != 0) { lua_pop(L, 1); /* -1 = 0 */ pthread_mutex_unlock(&cb->lock); ERROR("Lua plugin: luaC_pushvaluelist failed."); return -1; } /* +1 = 2 */ status = lua_pcall(L, 1, 1, 0); /* -2+1 = 1 */ if (status != 0) { const char *errmsg = lua_tostring(L, -1); if (errmsg == NULL) ERROR("Lua plugin: Calling the write callback failed. " "In addition, retrieving the error message failed."); else ERROR("Lua plugin: Calling the write callback failed:\n%s", errmsg); lua_pop(L, 1); /* -1 = 0 */ pthread_mutex_unlock(&cb->lock); return -1; } if (!lua_isnumber(L, -1)) { ERROR("Lua plugin: Write function \"%s\" (id %i) did not return a numeric " "value.", cb->lua_function_name, cb->callback_id); status = -1; } else { status = (int)lua_tointeger(L, -1); } lua_pop(L, 1); /* -1 = 0 */ pthread_mutex_unlock(&cb->lock); return status; } /* }}} int clua_write */ /* * Exported functions */ static int lua_cb_log_debug(lua_State *L) /* {{{ */ { const char *msg = luaL_checkstring(L, 1); plugin_log(LOG_DEBUG, "%s", msg); return 0; } /* }}} int lua_cb_log_debug */ static int lua_cb_log_error(lua_State *L) /* {{{ */ { const char *msg = luaL_checkstring(L, 1); plugin_log(LOG_ERR, "%s", msg); return 0; } /* }}} int lua_cb_log_error */ static int lua_cb_log_info(lua_State *L) /* {{{ */ { const char *msg = luaL_checkstring(L, 1); plugin_log(LOG_INFO, "%s", msg); return 0; } /* }}} int lua_cb_log_info */ static int lua_cb_log_notice(lua_State *L) /* {{{ */ { const char *msg = luaL_checkstring(L, 1); plugin_log(LOG_NOTICE, "%s", msg); return 0; } /* }}} int lua_cb_log_notice */ static int lua_cb_log_warning(lua_State *L) /* {{{ */ { const char *msg = luaL_checkstring(L, 1); plugin_log(LOG_WARNING, "%s", msg); return 0; } /* }}} int lua_cb_log_warning */ static int lua_cb_dispatch_values(lua_State *L) /* {{{ */ { int nargs = lua_gettop(L); if (nargs != 1) return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs); luaL_checktype(L, 1, LUA_TTABLE); value_list_t *vl = luaC_tovaluelist(L, -1); if (vl == NULL) return luaL_error(L, "%s", "luaC_tovaluelist failed"); #if COLLECT_DEBUG char identifier[6 * DATA_MAX_NAME_LEN]; FORMAT_VL(identifier, sizeof(identifier), vl); DEBUG("Lua plugin: collectd.dispatch_values(): Received value list \"%s\", " "time %.3f, interval %.3f.", identifier, CDTIME_T_TO_DOUBLE(vl->time), CDTIME_T_TO_DOUBLE(vl->interval)); #endif plugin_dispatch_values(vl); sfree(vl->values); sfree(vl); return 0; } /* }}} lua_cb_dispatch_values */ static void lua_cb_free(void *data) { clua_callback_data_t *cb = data; free(cb->lua_function_name); pthread_mutex_destroy(&cb->lock); free(cb); } static int lua_cb_register_generic(lua_State *L, int type) /* {{{ */ { int nargs = lua_gettop(L); if (nargs != 1) return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs); char subname[DATA_MAX_NAME_LEN]; if (!lua_isfunction(L, 1) && lua_isstring(L, 1)) { const char *fname = lua_tostring(L, 1); ssnprintf(subname, sizeof(subname), "%s()", fname); lua_getglobal(L, fname); // Push function into stack lua_remove(L, 1); // Remove string from stack if (!lua_isfunction(L, -1)) { return luaL_error(L, "Unable to find function '%s'", fname); } } else { lua_getfield(L, LUA_REGISTRYINDEX, "collectd:callback_num"); int tmp = lua_tointeger(L, -1); ssnprintf(subname, sizeof(subname), "callback_%d", tmp); lua_pop(L, 1); // Remove old value from stack lua_pushinteger(L, tmp + 1); lua_setfield(L, LUA_REGISTRYINDEX, "collectd:callback_num"); // pops value } luaL_checktype(L, 1, LUA_TFUNCTION); lua_getfield(L, LUA_REGISTRYINDEX, "collectd:script_path"); char function_name[DATA_MAX_NAME_LEN]; ssnprintf(function_name, sizeof(function_name), "lua/%s/%s", lua_tostring(L, -1), subname); lua_pop(L, 1); int callback_id = clua_store_callback(L, 1); if (callback_id < 0) return luaL_error(L, "%s", "Storing callback function failed"); lua_State *thread = lua_newthread(L); if (thread == NULL) return luaL_error(L, "%s", "lua_newthread failed"); clua_store_thread(L, -1); lua_pop(L, 1); clua_callback_data_t *cb = calloc(1, sizeof(*cb)); if (cb == NULL) return luaL_error(L, "%s", "calloc failed"); cb->lua_state = thread; cb->callback_id = callback_id; cb->lua_function_name = strdup(function_name); pthread_mutex_init(&cb->lock, NULL); if (PLUGIN_READ == type) { int status = plugin_register_complex_read(/* group = */ "lua", /* name = */ function_name, /* callback = */ clua_read, /* interval = */ 0, &(user_data_t){ .data = cb, .free_func = lua_cb_free, }); if (status != 0) return luaL_error(L, "%s", "plugin_register_complex_read failed"); return 0; } else if (PLUGIN_WRITE == type) { int status = plugin_register_write(/* name = */ function_name, /* callback = */ clua_write, &(user_data_t){ .data = cb, .free_func = lua_cb_free, }); if (status != 0) return luaL_error(L, "%s", "plugin_register_write failed"); return 0; } else { return luaL_error(L, "%s", "lua_cb_register_generic unsupported type"); } } /* }}} int lua_cb_register_generic */ static int lua_cb_register_read(lua_State *L) { return lua_cb_register_generic(L, PLUGIN_READ); } static int lua_cb_register_write(lua_State *L) { return lua_cb_register_generic(L, PLUGIN_WRITE); } static const luaL_Reg collectdlib[] = { {"log_debug", lua_cb_log_debug}, {"log_error", lua_cb_log_error}, {"log_info", lua_cb_log_info}, {"log_notice", lua_cb_log_notice}, {"log_warning", lua_cb_log_warning}, {"dispatch_values", lua_cb_dispatch_values}, {"register_read", lua_cb_register_read}, {"register_write", lua_cb_register_write}, {NULL, NULL}}; static int open_collectd(lua_State *L) /* {{{ */ { #if LUA_VERSION_NUM < 502 luaL_register(L, "collectd", collectdlib); #else luaL_newlib(L, collectdlib); #endif return 1; } /* }}} */ static void lua_script_free(lua_script_t *script) /* {{{ */ { if (script == NULL) return; lua_script_t *next = script->next; if (script->lua_state != NULL) { lua_close(script->lua_state); script->lua_state = NULL; } sfree(script); lua_script_free(next); } /* }}} void lua_script_free */ static int lua_script_init(lua_script_t *script) /* {{{ */ { memset(script, 0, sizeof(*script)); /* initialize the lua context */ script->lua_state = luaL_newstate(); if (script->lua_state == NULL) { ERROR("Lua plugin: luaL_newstate() failed."); return -1; } /* Open up all the standard Lua libraries. */ luaL_openlibs(script->lua_state); /* Load the 'collectd' library */ #if LUA_VERSION_NUM < 502 lua_pushcfunction(script->lua_state, open_collectd); lua_pushstring(script->lua_state, "collectd"); lua_call(script->lua_state, 1, 0); #else luaL_requiref(script->lua_state, "collectd", open_collectd, 1); lua_pop(script->lua_state, 1); #endif /* Prepend BasePath to package.path */ if (base_path[0] != '\0') { lua_getglobal(script->lua_state, "package"); lua_getfield(script->lua_state, -1, "path"); const char *cur_path = lua_tostring(script->lua_state, -1); char *new_path = ssnprintf_alloc("%s/?.lua;%s", base_path, cur_path); lua_pop(script->lua_state, 1); lua_pushstring(script->lua_state, new_path); free(new_path); lua_setfield(script->lua_state, -2, "path"); lua_pop(script->lua_state, 1); } return 0; } /* }}} int lua_script_init */ static int lua_script_load(const char *script_path) /* {{{ */ { lua_script_t *script = malloc(sizeof(*script)); if (script == NULL) { ERROR("Lua plugin: malloc failed."); return -1; } int status = lua_script_init(script); if (status != 0) { lua_script_free(script); return status; } status = luaL_loadfile(script->lua_state, script_path); if (status != 0) { ERROR("Lua plugin: luaL_loadfile failed: %s", lua_tostring(script->lua_state, -1)); lua_pop(script->lua_state, 1); lua_script_free(script); return -1; } lua_pushstring(script->lua_state, script_path); lua_setfield(script->lua_state, LUA_REGISTRYINDEX, "collectd:script_path"); lua_pushinteger(script->lua_state, 0); lua_setfield(script->lua_state, LUA_REGISTRYINDEX, "collectd:callback_num"); status = lua_pcall(script->lua_state, /* nargs = */ 0, /* nresults = */ LUA_MULTRET, /* errfunc = */ 0); if (status != 0) { const char *errmsg = lua_tostring(script->lua_state, -1); if (errmsg == NULL) ERROR("Lua plugin: lua_pcall failed with status %i. " "In addition, no error message could be retrieved from the stack.", status); else ERROR("Lua plugin: Executing script \"%s\" failed: %s", script_path, errmsg); } /* Append this script to the global list of scripts. */ if (scripts) { lua_script_t *last = scripts; while (last->next) last = last->next; last->next = script; } else { scripts = script; } if (status != 0) return -1; return 0; } /* }}} int lua_script_load */ static int lua_config_base_path(const oconfig_item_t *ci) /* {{{ */ { int status = cf_util_get_string_buffer(ci, base_path, sizeof(base_path)); if (status != 0) return status; size_t len = strlen(base_path); while ((len > 0) && (base_path[len - 1] == '/')) { len--; base_path[len] = '\0'; } DEBUG("Lua plugin: base_path = \"%s\";", base_path); return 0; } /* }}} int lua_config_base_path */ static int lua_config_script(const oconfig_item_t *ci) /* {{{ */ { char rel_path[PATH_MAX]; int status = cf_util_get_string_buffer(ci, rel_path, sizeof(rel_path)); if (status != 0) return status; char abs_path[PATH_MAX]; if (base_path[0] == '\0') sstrncpy(abs_path, rel_path, sizeof(abs_path)); else ssnprintf(abs_path, sizeof(abs_path), "%s/%s", base_path, rel_path); DEBUG("Lua plugin: abs_path = \"%s\";", abs_path); status = lua_script_load(abs_path); if (status != 0) return status; INFO("Lua plugin: File \"%s\" loaded successfully", abs_path); return 0; } /* }}} int lua_config_script */ /* * * BasePath "/" * Script "script1.lua" * Script "script2.lua" * */ static int lua_config(oconfig_item_t *ci) /* {{{ */ { int status = 0; for (int i = 0; i < ci->children_num; i++) { oconfig_item_t *child = ci->children + i; if (strcasecmp("BasePath", child->key) == 0) { status = lua_config_base_path(child); } else if (strcasecmp("Script", child->key) == 0) { status = lua_config_script(child); } else { ERROR("Lua plugin: Option `%s' is not allowed here.", child->key); status = 1; } } return status; } /* }}} int lua_config */ static int lua_shutdown(void) /* {{{ */ { lua_script_free(scripts); return 0; } /* }}} int lua_shutdown */ void module_register(void) { plugin_register_complex_config("lua", lua_config); plugin_register_shutdown("lua", lua_shutdown); }