ba5fbceac593a53ed33977fbf87e650284c883b0
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 static char base_path[PATH_MAX];
66 static lua_script_t *scripts;
68 static int clua_store_callback(lua_State *L, int idx) /* {{{ */
69 {
70 /* Copy the function pointer */
71 lua_pushvalue(L, idx);
73 return luaL_ref(L, LUA_REGISTRYINDEX);
74 } /* }}} int clua_store_callback */
76 static int clua_load_callback(lua_State *L, int callback_ref) /* {{{ */
77 {
78 lua_rawgeti(L, LUA_REGISTRYINDEX, callback_ref);
80 if (!lua_isfunction(L, -1)) {
81 lua_pop(L, 1);
82 return (-1);
83 }
85 return (0);
86 } /* }}} int clua_load_callback */
88 /* Store the threads in a global variable so they are not cleaned up by the
89 * garbage collector. */
90 static int clua_store_thread(lua_State *L, int idx) /* {{{ */
91 {
92 if (idx < 0)
93 idx += lua_gettop(L) + 1;
95 /* Copy the thread pointer */
96 lua_pushvalue(L, idx); /* +1 = 3 */
97 if (!lua_isthread(L, -1)) {
98 lua_pop(L, 3); /* -3 = 0 */
99 return (-1);
100 }
102 luaL_ref(L, LUA_REGISTRYINDEX);
103 lua_pop(L, 1); /* -1 = 0 */
104 return (0);
105 } /* }}} int clua_store_thread */
107 static int clua_read(user_data_t *ud) /* {{{ */
108 {
109 clua_callback_data_t *cb = ud->data;
111 pthread_mutex_lock(&cb->lock);
113 lua_State *L = cb->lua_state;
115 int status = clua_load_callback(L, cb->callback_id);
116 if (status != 0) {
117 ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).",
118 cb->lua_function_name, cb->callback_id);
119 pthread_mutex_unlock(&cb->lock);
120 return (-1);
121 }
122 /* +1 = 1 */
124 status = lua_pcall(L, 0, 1, 0);
125 if (status != 0) {
126 const char *errmsg = lua_tostring(L, -1);
127 if (errmsg == NULL)
128 ERROR("Lua plugin: Calling a read callback failed. "
129 "In addition, retrieving the error message failed.");
130 else
131 ERROR("Lua plugin: Calling a read callback failed: %s", errmsg);
132 lua_pop(L, 1);
133 pthread_mutex_unlock(&cb->lock);
134 return (-1);
135 }
137 if (!lua_isnumber(L, -1)) {
138 ERROR("Lua plugin: Read function \"%s\" (id %i) did not return a numeric "
139 "status.",
140 cb->lua_function_name, cb->callback_id);
141 status = -1;
142 } else {
143 status = (int)lua_tointeger(L, -1);
144 }
146 /* pop return value and function */
147 lua_pop(L, 1); /* -1 = 0 */
149 pthread_mutex_unlock(&cb->lock);
150 return (status);
151 } /* }}} int clua_read */
153 static int clua_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */
154 user_data_t *ud) {
155 clua_callback_data_t *cb = ud->data;
157 pthread_mutex_lock(&cb->lock);
159 lua_State *L = cb->lua_state;
161 int status = clua_load_callback(L, cb->callback_id);
162 if (status != 0) {
163 ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).",
164 cb->lua_function_name, cb->callback_id);
165 pthread_mutex_unlock(&cb->lock);
166 return (-1);
167 }
168 /* +1 = 1 */
170 status = luaC_pushvaluelist(L, ds, vl);
171 if (status != 0) {
172 lua_pop(L, 1); /* -1 = 0 */
173 pthread_mutex_unlock(&cb->lock);
174 ERROR("Lua plugin: luaC_pushvaluelist failed.");
175 return (-1);
176 }
177 /* +1 = 2 */
179 status = lua_pcall(L, 1, 1, 0); /* -2+1 = 1 */
180 if (status != 0) {
181 const char *errmsg = lua_tostring(L, -1);
182 if (errmsg == NULL)
183 ERROR("Lua plugin: Calling the write callback failed. "
184 "In addition, retrieving the error message failed.");
185 else
186 ERROR("Lua plugin: Calling the write callback failed:\n%s", errmsg);
187 lua_pop(L, 1); /* -1 = 0 */
188 pthread_mutex_unlock(&cb->lock);
189 return (-1);
190 }
192 if (!lua_isnumber(L, -1)) {
193 ERROR("Lua plugin: Write function \"%s\" (id %i) did not return a numeric "
194 "value.",
195 cb->lua_function_name, cb->callback_id);
196 status = -1;
197 } else {
198 status = (int)lua_tointeger(L, -1);
199 }
201 lua_pop(L, 1); /* -1 = 0 */
202 pthread_mutex_unlock(&cb->lock);
203 return (status);
204 } /* }}} int clua_write */
206 /*
207 * Exported functions
208 */
210 static int lua_cb_log_debug(lua_State *L) /* {{{ */
211 {
212 const char *msg = luaL_checkstring(L, 1);
213 plugin_log(LOG_DEBUG, "%s", msg);
214 return 0;
215 } /* }}} int lua_cb_log_debug */
217 static int lua_cb_log_error(lua_State *L) /* {{{ */
218 {
219 const char *msg = luaL_checkstring(L, 1);
220 plugin_log(LOG_ERR, "%s", msg);
221 return 0;
222 } /* }}} int lua_cb_log_error */
224 static int lua_cb_log_info(lua_State *L) /* {{{ */
225 {
226 const char *msg = luaL_checkstring(L, 1);
227 plugin_log(LOG_INFO, "%s", msg);
228 return 0;
229 } /* }}} int lua_cb_log_info */
231 static int lua_cb_log_notice(lua_State *L) /* {{{ */
232 {
233 const char *msg = luaL_checkstring(L, 1);
234 plugin_log(LOG_NOTICE, "%s", msg);
235 return 0;
236 } /* }}} int lua_cb_log_notice */
238 static int lua_cb_log_warning(lua_State *L) /* {{{ */
239 {
240 const char *msg = luaL_checkstring(L, 1);
241 plugin_log(LOG_WARNING, "%s", msg);
242 return 0;
243 } /* }}} int lua_cb_log_warning */
245 static int lua_cb_dispatch_values(lua_State *L) /* {{{ */
246 {
247 int nargs = lua_gettop(L);
249 if (nargs != 1)
250 return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
252 luaL_checktype(L, 1, LUA_TTABLE);
254 value_list_t *vl = luaC_tovaluelist(L, -1);
255 if (vl == NULL)
256 return luaL_error(L, "%s", "luaC_tovaluelist failed");
258 #if COLLECT_DEBUG
259 char identifier[6 * DATA_MAX_NAME_LEN];
260 FORMAT_VL(identifier, sizeof(identifier), vl);
262 DEBUG("Lua plugin: collectd.dispatch_values(): Received value list \"%s\", "
263 "time %.3f, interval %.3f.",
264 identifier, CDTIME_T_TO_DOUBLE(vl->time),
265 CDTIME_T_TO_DOUBLE(vl->interval));
266 #endif
268 plugin_dispatch_values(vl);
270 sfree(vl->values);
271 sfree(vl);
272 return 0;
273 } /* }}} lua_cb_dispatch_values */
275 static int lua_cb_register_read(lua_State *L) /* {{{ */
276 {
277 int nargs = lua_gettop(L);
279 if (nargs != 1)
280 return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
282 luaL_checktype(L, 1, LUA_TFUNCTION);
284 char function_name[DATA_MAX_NAME_LEN];
285 ssnprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
287 int callback_id = clua_store_callback(L, 1);
288 if (callback_id < 0)
289 return luaL_error(L, "%s", "Storing callback function failed");
291 lua_State *thread = lua_newthread(L);
292 if (thread == NULL)
293 return luaL_error(L, "%s", "lua_newthread failed");
294 clua_store_thread(L, -1);
295 lua_pop(L, 1);
297 clua_callback_data_t *cb = calloc(1, sizeof(*cb));
298 if (cb == NULL)
299 return luaL_error(L, "%s", "calloc failed");
301 cb->lua_state = thread;
302 cb->callback_id = callback_id;
303 cb->lua_function_name = strdup(function_name);
304 pthread_mutex_init(&cb->lock, NULL);
306 user_data_t ud = {
307 .data = cb
308 };
310 int status = plugin_register_complex_read(/* group = */ "lua",
311 /* name = */ function_name,
312 /* callback = */ clua_read,
313 /* interval = */ 0,
314 /* user_data = */ &ud);
316 if (status != 0)
317 return luaL_error(L, "%s", "plugin_register_complex_read failed");
318 return 0;
319 } /* }}} int lua_cb_register_read */
321 static int lua_cb_register_write(lua_State *L) /* {{{ */
322 {
323 int nargs = lua_gettop(L);
325 if (nargs != 1)
326 return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
328 luaL_checktype(L, 1, LUA_TFUNCTION);
330 char function_name[DATA_MAX_NAME_LEN] = "";
331 ssnprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
333 int callback_id = clua_store_callback(L, 1);
334 if (callback_id < 0)
335 return luaL_error(L, "%s", "Storing callback function failed");
337 lua_State *thread = lua_newthread(L);
338 if (thread == NULL)
339 return luaL_error(L, "%s", "lua_newthread failed");
340 clua_store_thread(L, -1);
341 lua_pop(L, 1);
343 clua_callback_data_t *cb = calloc(1, sizeof(*cb));
344 if (cb == NULL)
345 return luaL_error(L, "%s", "calloc failed");
347 cb->lua_state = thread;
348 cb->callback_id = callback_id;
349 cb->lua_function_name = strdup(function_name);
350 pthread_mutex_init(&cb->lock, NULL);
352 user_data_t ud = {
353 .data = cb
354 };
356 int status = plugin_register_write(/* name = */ function_name,
357 /* callback = */ clua_write,
358 /* user_data = */ &ud);
360 if (status != 0)
361 return luaL_error(L, "%s", "plugin_register_write failed");
362 return 0;
363 } /* }}} int lua_cb_register_write */
365 static const luaL_Reg collectdlib[] = {
366 {"log_debug", lua_cb_log_debug},
367 {"log_error", lua_cb_log_error},
368 {"log_info", lua_cb_log_info},
369 {"log_notice", lua_cb_log_notice},
370 {"log_warning", lua_cb_log_warning},
371 {"dispatch_values", lua_cb_dispatch_values},
372 {"register_read", lua_cb_register_read},
373 {"register_write", lua_cb_register_write},
374 {NULL, NULL}
375 };
377 static int open_collectd(lua_State *L) /* {{{ */
378 {
379 #if LUA_VERSION_NUM < 502
380 luaL_register(L, "collectd", collectdlib);
381 #else
382 luaL_newlib(L, collectdlib);
383 #endif
384 return 1;
385 } /* }}} */
387 static void lua_script_free(lua_script_t *script) /* {{{ */
388 {
389 if (script == NULL)
390 return;
392 lua_script_t *next = script->next;
394 if (script->lua_state != NULL) {
395 lua_close(script->lua_state);
396 script->lua_state = NULL;
397 }
399 sfree(script->script_path);
400 sfree(script);
402 lua_script_free(next);
403 } /* }}} void lua_script_free */
405 static int lua_script_init(lua_script_t *script) /* {{{ */
406 {
407 memset(script, 0, sizeof(*script));
409 /* initialize the lua context */
410 script->lua_state = luaL_newstate();
411 if (script->lua_state == NULL) {
412 ERROR("Lua plugin: luaL_newstate() failed.");
413 return (-1);
414 }
416 /* Open up all the standard Lua libraries. */
417 luaL_openlibs(script->lua_state);
419 /* Load the 'collectd' library */
420 #if LUA_VERSION_NUM < 502
421 lua_pushcfunction(script->lua_state, open_collectd);
422 lua_pushstring(script->lua_state, "collectd");
423 lua_call(script->lua_state, 1, 0);
424 #else
425 luaL_requiref(script->lua_state, "collectd", open_collectd, 1);
426 lua_pop(script->lua_state, 1);
427 #endif
429 /* Prepend BasePath to package.path */
430 if (base_path[0] != '\0') {
431 lua_getglobal(script->lua_state, "package");
432 lua_getfield(script->lua_state, -1, "path");
434 const char *cur_path = lua_tostring(script->lua_state, -1);
435 char *new_path = ssnprintf_alloc("%s/?.lua;%s", base_path, cur_path);
437 lua_pop(script->lua_state, 1);
438 lua_pushstring(script->lua_state, new_path);
440 free(new_path);
442 lua_setfield(script->lua_state, -2, "path");
443 lua_pop(script->lua_state, 1);
444 }
446 return (0);
447 } /* }}} int lua_script_init */
449 static int lua_script_load(const char *script_path) /* {{{ */
450 {
451 lua_script_t *script = malloc(sizeof(*script));
452 if (script == NULL) {
453 ERROR("Lua plugin: malloc failed.");
454 return (-1);
455 }
457 int status = lua_script_init(script);
458 if (status != 0) {
459 lua_script_free(script);
460 return (status);
461 }
463 script->script_path = strdup(script_path);
464 if (script->script_path == NULL) {
465 ERROR("Lua plugin: strdup failed.");
466 lua_script_free(script);
467 return (-1);
468 }
470 status = luaL_loadfile(script->lua_state, script->script_path);
471 if (status != 0) {
472 ERROR("Lua plugin: luaL_loadfile failed: %s",
473 lua_tostring(script->lua_state, -1));
474 lua_pop(script->lua_state, 1);
475 lua_script_free(script);
476 return (-1);
477 }
479 status = lua_pcall(script->lua_state,
480 /* nargs = */ 0,
481 /* nresults = */ LUA_MULTRET,
482 /* errfunc = */ 0);
483 if (status != 0) {
484 const char *errmsg = lua_tostring(script->lua_state, -1);
486 if (errmsg == NULL)
487 ERROR("Lua plugin: lua_pcall failed with status %i. "
488 "In addition, no error message could be retrieved from the stack.",
489 status);
490 else
491 ERROR("Lua plugin: Executing script \"%s\" failed:\n%s",
492 script->script_path, errmsg);
494 lua_script_free(script);
495 return (-1);
496 }
498 /* Append this script to the global list of scripts. */
499 if (scripts) {
500 lua_script_t *last = scripts;
501 while (last->next)
502 last = last->next;
504 last->next = script;
505 } else {
506 scripts = script;
507 }
509 return (0);
510 } /* }}} int lua_script_load */
512 static int lua_config_base_path(const oconfig_item_t *ci) /* {{{ */
513 {
514 int status = cf_util_get_string_buffer(ci, base_path, sizeof(base_path));
515 if (status != 0)
516 return (status);
518 size_t len = strlen(base_path);
519 while ((len > 0) && (base_path[len - 1] == '/')) {
520 len--;
521 base_path[len] = '\0';
522 }
524 DEBUG("Lua plugin: base_path = \"%s\";", base_path);
526 return (0);
527 } /* }}} int lua_config_base_path */
529 static int lua_config_script(const oconfig_item_t *ci) /* {{{ */
530 {
531 char rel_path[PATH_MAX];
533 int status = cf_util_get_string_buffer(ci, rel_path, sizeof(rel_path));
534 if (status != 0)
535 return (status);
537 char abs_path[PATH_MAX];
539 if (base_path[0] == '\0')
540 sstrncpy(abs_path, rel_path, sizeof(abs_path));
541 else
542 ssnprintf(abs_path, sizeof(abs_path), "%s/%s", base_path, rel_path);
544 DEBUG("Lua plugin: abs_path = \"%s\";", abs_path);
546 status = lua_script_load(abs_path);
547 if (status != 0)
548 return (status);
550 INFO("Lua plugin: File \"%s\" loaded succesfully", abs_path);
552 return 0;
553 } /* }}} int lua_config_script */
555 /*
556 * <Plugin lua>
557 * BasePath "/"
558 * Script "script1.lua"
559 * Script "script2.lua"
560 * </Plugin>
561 */
562 static int lua_config(oconfig_item_t *ci) /* {{{ */
563 {
564 int status = 0;
565 for (int i = 0; i < ci->children_num; i++) {
566 oconfig_item_t *child = ci->children + i;
568 if (strcasecmp("BasePath", child->key) == 0) {
569 status = lua_config_base_path(child);
570 } else if (strcasecmp("Script", child->key) == 0) {
571 status = lua_config_script(child);
572 } else {
573 ERROR("Lua plugin: Option `%s' is not allowed here.", child->key);
574 status = 1;
575 }
576 }
578 return status;
579 } /* }}} int lua_config */
581 static int lua_shutdown(void) /* {{{ */
582 {
583 lua_script_free(scripts);
585 return (0);
586 } /* }}} int lua_shutdown */
588 void module_register(void) {
589 plugin_register_complex_config("lua", lua_config);
590 plugin_register_shutdown("lua", lua_shutdown);
591 }
593 /* vim: set sw=2 sts=2 et fdm=marker : */