b89c824001f6bffc1770ae80914a8145e7f9484d
1 /**
2 * collectd - src/lua.c
3 * Copyright (C) 2010 Julien Ammous
4 * Copyright (C) 2010 Florian Forster
5 * Copyright (C) 2016 Ruben Kerkhof
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 *
25 * Authors:
26 * Julien Ammous
27 * Florian Forster <octo at collectd.org>
28 * Ruben Kerkhof <ruben at rubenkerkhof.com>
29 **/
31 /* <lua5.1/luaconf.h> defines a macro using "sprintf". Although not used here,
32 * GCC will complain about the macro definition. */
33 #define DONT_POISON_SPRINTF_YET
35 #include "collectd.h"
36 #include "common.h"
37 #include "plugin.h"
39 /* Include the Lua API header files. */
40 #include "utils_lua.h"
41 #include <lauxlib.h>
42 #include <lua.h>
43 #include <lualib.h>
45 #include <pthread.h>
47 #if COLLECT_DEBUG && __GNUC__
48 #undef sprintf
49 #pragma GCC poison sprintf
50 #endif
52 typedef struct lua_script_s {
53 char *script_path;
54 lua_State *lua_state;
55 struct lua_script_s *next;
56 } lua_script_t;
58 typedef struct {
59 lua_State *lua_state;
60 const char *lua_function_name;
61 pthread_mutex_t lock;
62 int callback_id;
63 } clua_callback_data_t;
65 typedef struct {
66 const char *name;
67 lua_CFunction func;
68 } lua_c_function_t;
70 static char base_path[PATH_MAX];
71 static lua_script_t *scripts;
73 static int clua_store_callback(lua_State *L, int idx) /* {{{ */
74 {
75 /* Copy the function pointer */
76 lua_pushvalue(L, idx);
78 return luaL_ref(L, LUA_REGISTRYINDEX);
79 } /* }}} int clua_store_callback */
81 static int clua_load_callback(lua_State *L, int callback_ref) /* {{{ */
82 {
83 lua_rawgeti(L, LUA_REGISTRYINDEX, callback_ref);
85 if (!lua_isfunction(L, -1)) {
86 lua_pop(L, 1);
87 return (-1);
88 }
90 return (0);
91 } /* }}} int clua_load_callback */
93 /* Store the threads in a global variable so they are not cleaned up by the
94 * garbage collector. */
95 static int clua_store_thread(lua_State *L, int idx) /* {{{ */
96 {
97 if (idx < 0)
98 idx += lua_gettop(L) + 1;
100 /* Copy the thread pointer */
101 lua_pushvalue(L, idx); /* +1 = 3 */
102 if (!lua_isthread(L, -1)) {
103 lua_pop(L, 3); /* -3 = 0 */
104 return (-1);
105 }
107 luaL_ref(L, LUA_REGISTRYINDEX);
108 lua_pop(L, 1); /* -1 = 0 */
109 return (0);
110 } /* }}} int clua_store_thread */
112 static int clua_read(user_data_t *ud) /* {{{ */
113 {
114 clua_callback_data_t *cb = ud->data;
116 pthread_mutex_lock(&cb->lock);
118 lua_State *L = cb->lua_state;
120 int status = clua_load_callback(L, cb->callback_id);
121 if (status != 0) {
122 ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).",
123 cb->lua_function_name, cb->callback_id);
124 pthread_mutex_unlock(&cb->lock);
125 return (-1);
126 }
127 /* +1 = 1 */
129 status = lua_pcall(L, 0, 1, 0);
130 if (status != 0) {
131 const char *errmsg = lua_tostring(L, -1);
132 if (errmsg == NULL)
133 ERROR("Lua plugin: Calling a read callback failed. "
134 "In addition, retrieving the error message failed.");
135 else
136 ERROR("Lua plugin: Calling a read callback failed: %s", errmsg);
137 lua_pop(L, 1);
138 pthread_mutex_unlock(&cb->lock);
139 return (-1);
140 }
142 if (!lua_isnumber(L, -1)) {
143 ERROR("Lua plugin: Read function \"%s\" (id %i) did not return a numeric "
144 "status.",
145 cb->lua_function_name, cb->callback_id);
146 status = -1;
147 } else {
148 status = (int)lua_tointeger(L, -1);
149 }
151 /* pop return value and function */
152 lua_pop(L, 1); /* -1 = 0 */
154 pthread_mutex_unlock(&cb->lock);
155 return (status);
156 } /* }}} int clua_read */
158 static int clua_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */
159 user_data_t *ud) {
160 clua_callback_data_t *cb = ud->data;
162 pthread_mutex_lock(&cb->lock);
164 lua_State *L = cb->lua_state;
166 int status = clua_load_callback(L, cb->callback_id);
167 if (status != 0) {
168 ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).",
169 cb->lua_function_name, cb->callback_id);
170 pthread_mutex_unlock(&cb->lock);
171 return (-1);
172 }
173 /* +1 = 1 */
175 status = luaC_pushvaluelist(L, ds, vl);
176 if (status != 0) {
177 lua_pop(L, 1); /* -1 = 0 */
178 pthread_mutex_unlock(&cb->lock);
179 ERROR("Lua plugin: luaC_pushvaluelist failed.");
180 return (-1);
181 }
182 /* +1 = 2 */
184 status = lua_pcall(L, 1, 1, 0); /* -2+1 = 1 */
185 if (status != 0) {
186 const char *errmsg = lua_tostring(L, -1);
187 if (errmsg == NULL)
188 ERROR("Lua plugin: Calling the write callback failed. "
189 "In addition, retrieving the error message failed.");
190 else
191 ERROR("Lua plugin: Calling the write callback failed:\n%s", errmsg);
192 lua_pop(L, 1); /* -1 = 0 */
193 pthread_mutex_unlock(&cb->lock);
194 return (-1);
195 }
197 if (!lua_isnumber(L, -1)) {
198 ERROR("Lua plugin: Write function \"%s\" (id %i) did not return a numeric "
199 "value.",
200 cb->lua_function_name, cb->callback_id);
201 status = -1;
202 } else {
203 status = (int)lua_tointeger(L, -1);
204 }
206 lua_pop(L, 1); /* -1 = 0 */
207 pthread_mutex_unlock(&cb->lock);
208 return (status);
209 } /* }}} int clua_write */
211 /*
212 * Exported functions
213 */
215 static int lua_cb_log_debug(lua_State *L) /* {{{ */
216 {
217 const char *msg = luaL_checkstring(L, 1);
218 plugin_log(LOG_DEBUG, "%s", msg);
219 return 0;
220 } /* }}} int lua_cb_log_debug */
222 static int lua_cb_log_error(lua_State *L) /* {{{ */
223 {
224 const char *msg = luaL_checkstring(L, 1);
225 plugin_log(LOG_ERR, "%s", msg);
226 return 0;
227 } /* }}} int lua_cb_log_error */
229 static int lua_cb_log_info(lua_State *L) /* {{{ */
230 {
231 const char *msg = luaL_checkstring(L, 1);
232 plugin_log(LOG_INFO, "%s", msg);
233 return 0;
234 } /* }}} int lua_cb_log_info */
236 static int lua_cb_log_notice(lua_State *L) /* {{{ */
237 {
238 const char *msg = luaL_checkstring(L, 1);
239 plugin_log(LOG_NOTICE, "%s", msg);
240 return 0;
241 } /* }}} int lua_cb_log_notice */
243 static int lua_cb_log_warning(lua_State *L) /* {{{ */
244 {
245 const char *msg = luaL_checkstring(L, 1);
246 plugin_log(LOG_WARNING, "%s", msg);
247 return 0;
248 } /* }}} int lua_cb_log_warning */
250 static int lua_cb_dispatch_values(lua_State *L) /* {{{ */
251 {
252 int nargs = lua_gettop(L);
254 if (nargs != 1)
255 return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
257 luaL_checktype(L, 1, LUA_TTABLE);
259 value_list_t *vl = luaC_tovaluelist(L, -1);
260 if (vl == NULL)
261 return luaL_error(L, "%s", "luaC_tovaluelist failed");
263 #if COLLECT_DEBUG
264 char identifier[6 * DATA_MAX_NAME_LEN];
265 FORMAT_VL(identifier, sizeof(identifier), vl);
267 DEBUG("Lua plugin: collectd.dispatch_values(): Received value list \"%s\", "
268 "time %.3f, interval %.3f.",
269 identifier, CDTIME_T_TO_DOUBLE(vl->time),
270 CDTIME_T_TO_DOUBLE(vl->interval));
271 #endif
273 plugin_dispatch_values(vl);
275 sfree(vl->values);
276 sfree(vl);
277 return 0;
278 } /* }}} lua_cb_dispatch_values */
280 static int lua_cb_register_read(lua_State *L) /* {{{ */
281 {
282 int nargs = lua_gettop(L);
284 if (nargs != 1)
285 return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
287 luaL_checktype(L, 1, LUA_TFUNCTION);
289 char function_name[DATA_MAX_NAME_LEN];
290 ssnprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
292 int callback_id = clua_store_callback(L, 1);
293 if (callback_id < 0)
294 return luaL_error(L, "%s", "Storing callback function failed");
296 lua_State *thread = lua_newthread(L);
297 if (thread == NULL)
298 return luaL_error(L, "%s", "lua_newthread failed");
299 clua_store_thread(L, -1);
300 lua_pop(L, 1);
302 clua_callback_data_t *cb = calloc(1, sizeof(*cb));
303 if (cb == NULL)
304 return luaL_error(L, "%s", "calloc failed");
306 cb->lua_state = thread;
307 cb->callback_id = callback_id;
308 cb->lua_function_name = strdup(function_name);
309 pthread_mutex_init(&cb->lock, NULL);
311 user_data_t ud = {
312 .data = cb
313 };
315 int status = plugin_register_complex_read(/* group = */ "lua",
316 /* name = */ function_name,
317 /* callback = */ clua_read,
318 /* interval = */ 0,
319 /* user_data = */ &ud);
321 if (status != 0)
322 return luaL_error(L, "%s", "plugin_register_complex_read failed");
323 return 0;
324 } /* }}} int lua_cb_register_read */
326 static int lua_cb_register_write(lua_State *L) /* {{{ */
327 {
328 int nargs = lua_gettop(L);
330 if (nargs != 1)
331 return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
333 luaL_checktype(L, 1, LUA_TFUNCTION);
335 char function_name[DATA_MAX_NAME_LEN] = "";
336 ssnprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
338 int callback_id = clua_store_callback(L, 1);
339 if (callback_id < 0)
340 return luaL_error(L, "%s", "Storing callback function failed");
342 lua_State *thread = lua_newthread(L);
343 if (thread == NULL)
344 return luaL_error(L, "%s", "lua_newthread failed");
345 clua_store_thread(L, -1);
346 lua_pop(L, 1);
348 clua_callback_data_t *cb = calloc(1, sizeof(*cb));
349 if (cb == NULL)
350 return luaL_error(L, "%s", "calloc failed");
352 cb->lua_state = thread;
353 cb->callback_id = callback_id;
354 cb->lua_function_name = strdup(function_name);
355 pthread_mutex_init(&cb->lock, NULL);
357 user_data_t ud = {
358 .data = cb
359 };
361 int status = plugin_register_write(/* name = */ function_name,
362 /* callback = */ clua_write,
363 /* user_data = */ &ud);
365 if (status != 0)
366 return luaL_error(L, "%s", "plugin_register_write failed");
367 return 0;
368 } /* }}} int lua_cb_register_write */
370 static lua_c_function_t lua_c_functions[] = {
371 {"log_debug", lua_cb_log_debug},
372 {"log_error", lua_cb_log_error},
373 {"log_info", lua_cb_log_info},
374 {"log_notice", lua_cb_log_notice},
375 {"log_warning", lua_cb_log_warning},
376 {"dispatch_values", lua_cb_dispatch_values},
377 {"register_read", lua_cb_register_read},
378 {"register_write", lua_cb_register_write}};
380 static void lua_script_free(lua_script_t *script) /* {{{ */
381 {
382 if (script == NULL)
383 return;
385 lua_script_t *next = script->next;
387 if (script->lua_state != NULL) {
388 lua_close(script->lua_state);
389 script->lua_state = NULL;
390 }
392 sfree(script->script_path);
393 sfree(script);
395 lua_script_free(next);
396 } /* }}} void lua_script_free */
398 static int lua_script_init(lua_script_t *script) /* {{{ */
399 {
400 memset(script, 0, sizeof(*script));
402 /* initialize the lua context */
403 script->lua_state = luaL_newstate();
404 if (script->lua_state == NULL) {
405 ERROR("Lua plugin: luaL_newstate() failed.");
406 return (-1);
407 }
409 /* Open up all the standard Lua libraries. */
410 luaL_openlibs(script->lua_state);
412 /* Register all the functions we implement in C */
413 lua_newtable(script->lua_state);
414 for (size_t i = 0; i < STATIC_ARRAY_SIZE(lua_c_functions); i++) {
415 lua_pushcfunction(script->lua_state, lua_c_functions[i].func);
416 lua_setfield(script->lua_state, -2, lua_c_functions[i].name);
417 }
418 lua_setglobal(script->lua_state, "collectd");
420 /* Prepend BasePath to package.path */
421 if (base_path[0] != '\0') {
422 lua_getglobal(script->lua_state, "package");
423 lua_getfield(script->lua_state, -1, "path");
425 const char *cur_path = lua_tostring(script->lua_state, -1);
426 char *new_path = ssnprintf_alloc("%s/?.lua;%s", base_path, cur_path);
428 lua_pop(script->lua_state, 1);
429 lua_pushstring(script->lua_state, new_path);
431 free(new_path);
433 lua_setfield(script->lua_state, -2, "path");
434 lua_pop(script->lua_state, 1);
435 }
437 return (0);
438 } /* }}} int lua_script_init */
440 static int lua_script_load(const char *script_path) /* {{{ */
441 {
442 lua_script_t *script = malloc(sizeof(*script));
443 if (script == NULL) {
444 ERROR("Lua plugin: malloc failed.");
445 return (-1);
446 }
448 int status = lua_script_init(script);
449 if (status != 0) {
450 lua_script_free(script);
451 return (status);
452 }
454 script->script_path = strdup(script_path);
455 if (script->script_path == NULL) {
456 ERROR("Lua plugin: strdup failed.");
457 lua_script_free(script);
458 return (-1);
459 }
461 status = luaL_loadfile(script->lua_state, script->script_path);
462 if (status != 0) {
463 ERROR("Lua plugin: luaL_loadfile failed: %s",
464 lua_tostring(script->lua_state, -1));
465 lua_pop(script->lua_state, 1);
466 lua_script_free(script);
467 return (-1);
468 }
470 status = lua_pcall(script->lua_state,
471 /* nargs = */ 0,
472 /* nresults = */ LUA_MULTRET,
473 /* errfunc = */ 0);
474 if (status != 0) {
475 const char *errmsg = lua_tostring(script->lua_state, -1);
477 if (errmsg == NULL)
478 ERROR("Lua plugin: lua_pcall failed with status %i. "
479 "In addition, no error message could be retrieved from the stack.",
480 status);
481 else
482 ERROR("Lua plugin: Executing script \"%s\" failed:\n%s",
483 script->script_path, errmsg);
485 lua_script_free(script);
486 return (-1);
487 }
489 /* Append this script to the global list of scripts. */
490 if (scripts) {
491 lua_script_t *last = scripts;
492 while (last->next)
493 last = last->next;
495 last->next = script;
496 } else {
497 scripts = script;
498 }
500 return (0);
501 } /* }}} int lua_script_load */
503 static int lua_config_base_path(const oconfig_item_t *ci) /* {{{ */
504 {
505 int status = cf_util_get_string_buffer(ci, base_path, sizeof(base_path));
506 if (status != 0)
507 return (status);
509 size_t len = strlen(base_path);
510 while ((len > 0) && (base_path[len - 1] == '/')) {
511 len--;
512 base_path[len] = '\0';
513 }
515 DEBUG("Lua plugin: base_path = \"%s\";", base_path);
517 return (0);
518 } /* }}} int lua_config_base_path */
520 static int lua_config_script(const oconfig_item_t *ci) /* {{{ */
521 {
522 char rel_path[PATH_MAX];
524 int status = cf_util_get_string_buffer(ci, rel_path, sizeof(rel_path));
525 if (status != 0)
526 return (status);
528 char abs_path[PATH_MAX];
530 if (base_path[0] == '\0')
531 sstrncpy(abs_path, rel_path, sizeof(abs_path));
532 else
533 ssnprintf(abs_path, sizeof(abs_path), "%s/%s", base_path, rel_path);
535 DEBUG("Lua plugin: abs_path = \"%s\";", abs_path);
537 status = lua_script_load(abs_path);
538 if (status != 0)
539 return (status);
541 INFO("Lua plugin: File \"%s\" loaded succesfully", abs_path);
543 return 0;
544 } /* }}} int lua_config_script */
546 /*
547 * <Plugin lua>
548 * BasePath "/"
549 * Script "script1.lua"
550 * Script "script2.lua"
551 * </Plugin>
552 */
553 static int lua_config(oconfig_item_t *ci) /* {{{ */
554 {
555 int status = 0;
556 for (int i = 0; i < ci->children_num; i++) {
557 oconfig_item_t *child = ci->children + i;
559 if (strcasecmp("BasePath", child->key) == 0) {
560 status = lua_config_base_path(child);
561 } else if (strcasecmp("Script", child->key) == 0) {
562 status = lua_config_script(child);
563 } else {
564 ERROR("Lua plugin: Option `%s' is not allowed here.", child->key);
565 status = 1;
566 }
567 }
569 return status;
570 } /* }}} int lua_config */
572 static int lua_shutdown(void) /* {{{ */
573 {
574 lua_script_free(scripts);
576 return (0);
577 } /* }}} int lua_shutdown */
579 void module_register() {
580 plugin_register_complex_config("lua", lua_config);
581 plugin_register_shutdown("lua", lua_shutdown);
582 }
584 /* vim: set sw=2 sts=2 et fdm=marker : */