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 **/
30
31 #include "collectd.h"
32 #include "plugin.h"
33 #include "utils/common/common.h"
34 #include "utils_lua.h"
35
36 /* Include the Lua API header files. */
37 #include <lauxlib.h>
38 #include <lua.h>
39 #include <lualib.h>
40
41 #include <pthread.h>
42
43 #define PLUGIN_READ 1
44 #define PLUGIN_WRITE 2
45
46 typedef struct lua_script_s {
47 lua_State *lua_state;
48 struct lua_script_s *next;
49 } lua_script_t;
50
51 typedef struct {
52 lua_State *lua_state;
53 char *lua_function_name;
54 pthread_mutex_t lock;
55 int callback_id;
56 } clua_callback_data_t;
57
58 static char base_path[PATH_MAX];
59 static lua_script_t *scripts;
60
clua_store_callback(lua_State * L,int idx)61 static int clua_store_callback(lua_State *L, int idx) /* {{{ */
62 {
63 /* Copy the function pointer */
64 lua_pushvalue(L, idx);
65
66 return luaL_ref(L, LUA_REGISTRYINDEX);
67 } /* }}} int clua_store_callback */
68
clua_load_callback(lua_State * L,int callback_ref)69 static int clua_load_callback(lua_State *L, int callback_ref) /* {{{ */
70 {
71 lua_rawgeti(L, LUA_REGISTRYINDEX, callback_ref);
72
73 if (!lua_isfunction(L, -1)) {
74 lua_pop(L, 1);
75 return -1;
76 }
77
78 return 0;
79 } /* }}} int clua_load_callback */
80
81 /* Store the threads in a global variable so they are not cleaned up by the
82 * garbage collector. */
clua_store_thread(lua_State * L,int idx)83 static int clua_store_thread(lua_State *L, int idx) /* {{{ */
84 {
85 if (!lua_isthread(L, idx)) {
86 return -1;
87 }
88
89 /* Copy the thread pointer */
90 lua_pushvalue(L, idx);
91
92 luaL_ref(L, LUA_REGISTRYINDEX);
93 return 0;
94 } /* }}} int clua_store_thread */
95
clua_read(user_data_t * ud)96 static int clua_read(user_data_t *ud) /* {{{ */
97 {
98 clua_callback_data_t *cb = ud->data;
99
100 pthread_mutex_lock(&cb->lock);
101
102 lua_State *L = cb->lua_state;
103
104 int status = clua_load_callback(L, cb->callback_id);
105 if (status != 0) {
106 ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).",
107 cb->lua_function_name, cb->callback_id);
108 pthread_mutex_unlock(&cb->lock);
109 return -1;
110 }
111 /* +1 = 1 */
112
113 status = lua_pcall(L, 0, 1, 0);
114 if (status != 0) {
115 const char *errmsg = lua_tostring(L, -1);
116 if (errmsg == NULL)
117 ERROR("Lua plugin: Calling a read callback failed. "
118 "In addition, retrieving the error message failed.");
119 else
120 ERROR("Lua plugin: Calling a read callback failed: %s", errmsg);
121 lua_pop(L, 1);
122 pthread_mutex_unlock(&cb->lock);
123 return -1;
124 }
125
126 if (!lua_isnumber(L, -1)) {
127 ERROR("Lua plugin: Read function \"%s\" (id %i) did not return a numeric "
128 "status.",
129 cb->lua_function_name, cb->callback_id);
130 status = -1;
131 } else {
132 status = (int)lua_tointeger(L, -1);
133 }
134
135 /* pop return value and function */
136 lua_pop(L, 1); /* -1 = 0 */
137
138 pthread_mutex_unlock(&cb->lock);
139 return status;
140 } /* }}} int clua_read */
141
clua_write(const data_set_t * ds,const value_list_t * vl,user_data_t * ud)142 static int clua_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */
143 user_data_t *ud) {
144 clua_callback_data_t *cb = ud->data;
145
146 pthread_mutex_lock(&cb->lock);
147
148 lua_State *L = cb->lua_state;
149
150 int status = clua_load_callback(L, cb->callback_id);
151 if (status != 0) {
152 ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).",
153 cb->lua_function_name, cb->callback_id);
154 pthread_mutex_unlock(&cb->lock);
155 return -1;
156 }
157 /* +1 = 1 */
158
159 status = luaC_pushvaluelist(L, ds, vl);
160 if (status != 0) {
161 lua_pop(L, 1); /* -1 = 0 */
162 pthread_mutex_unlock(&cb->lock);
163 ERROR("Lua plugin: luaC_pushvaluelist failed.");
164 return -1;
165 }
166 /* +1 = 2 */
167
168 status = lua_pcall(L, 1, 1, 0); /* -2+1 = 1 */
169 if (status != 0) {
170 const char *errmsg = lua_tostring(L, -1);
171 if (errmsg == NULL)
172 ERROR("Lua plugin: Calling the write callback failed. "
173 "In addition, retrieving the error message failed.");
174 else
175 ERROR("Lua plugin: Calling the write callback failed:\n%s", errmsg);
176 lua_pop(L, 1); /* -1 = 0 */
177 pthread_mutex_unlock(&cb->lock);
178 return -1;
179 }
180
181 if (!lua_isnumber(L, -1)) {
182 ERROR("Lua plugin: Write function \"%s\" (id %i) did not return a numeric "
183 "value.",
184 cb->lua_function_name, cb->callback_id);
185 status = -1;
186 } else {
187 status = (int)lua_tointeger(L, -1);
188 }
189
190 lua_pop(L, 1); /* -1 = 0 */
191 pthread_mutex_unlock(&cb->lock);
192 return status;
193 } /* }}} int clua_write */
194
195 /*
196 * Exported functions
197 */
198
lua_cb_log_debug(lua_State * L)199 static int lua_cb_log_debug(lua_State *L) /* {{{ */
200 {
201 const char *msg = luaL_checkstring(L, 1);
202 plugin_log(LOG_DEBUG, "%s", msg);
203 return 0;
204 } /* }}} int lua_cb_log_debug */
205
lua_cb_log_error(lua_State * L)206 static int lua_cb_log_error(lua_State *L) /* {{{ */
207 {
208 const char *msg = luaL_checkstring(L, 1);
209 plugin_log(LOG_ERR, "%s", msg);
210 return 0;
211 } /* }}} int lua_cb_log_error */
212
lua_cb_log_info(lua_State * L)213 static int lua_cb_log_info(lua_State *L) /* {{{ */
214 {
215 const char *msg = luaL_checkstring(L, 1);
216 plugin_log(LOG_INFO, "%s", msg);
217 return 0;
218 } /* }}} int lua_cb_log_info */
219
lua_cb_log_notice(lua_State * L)220 static int lua_cb_log_notice(lua_State *L) /* {{{ */
221 {
222 const char *msg = luaL_checkstring(L, 1);
223 plugin_log(LOG_NOTICE, "%s", msg);
224 return 0;
225 } /* }}} int lua_cb_log_notice */
226
lua_cb_log_warning(lua_State * L)227 static int lua_cb_log_warning(lua_State *L) /* {{{ */
228 {
229 const char *msg = luaL_checkstring(L, 1);
230 plugin_log(LOG_WARNING, "%s", msg);
231 return 0;
232 } /* }}} int lua_cb_log_warning */
233
lua_cb_dispatch_values(lua_State * L)234 static int lua_cb_dispatch_values(lua_State *L) /* {{{ */
235 {
236 int nargs = lua_gettop(L);
237
238 if (nargs != 1)
239 return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
240
241 luaL_checktype(L, 1, LUA_TTABLE);
242
243 value_list_t *vl = luaC_tovaluelist(L, -1);
244 if (vl == NULL)
245 return luaL_error(L, "%s", "luaC_tovaluelist failed");
246
247 #if COLLECT_DEBUG
248 char identifier[6 * DATA_MAX_NAME_LEN];
249 FORMAT_VL(identifier, sizeof(identifier), vl);
250
251 DEBUG("Lua plugin: collectd.dispatch_values(): Received value list \"%s\", "
252 "time %.3f, interval %.3f.",
253 identifier, CDTIME_T_TO_DOUBLE(vl->time),
254 CDTIME_T_TO_DOUBLE(vl->interval));
255 #endif
256
257 plugin_dispatch_values(vl);
258
259 sfree(vl->values);
260 sfree(vl);
261 return 0;
262 } /* }}} lua_cb_dispatch_values */
263
lua_cb_free(void * data)264 static void lua_cb_free(void *data) {
265 clua_callback_data_t *cb = data;
266 free(cb->lua_function_name);
267 pthread_mutex_destroy(&cb->lock);
268 free(cb);
269 }
270
lua_cb_register_generic(lua_State * L,int type)271 static int lua_cb_register_generic(lua_State *L, int type) /* {{{ */
272 {
273 int nargs = lua_gettop(L);
274
275 if (nargs != 1)
276 return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
277
278 char subname[DATA_MAX_NAME_LEN];
279 if (!lua_isfunction(L, 1) && lua_isstring(L, 1)) {
280 const char *fname = lua_tostring(L, 1);
281 ssnprintf(subname, sizeof(subname), "%s()", fname);
282
283 lua_getglobal(L, fname); // Push function into stack
284 lua_remove(L, 1); // Remove string from stack
285 if (!lua_isfunction(L, -1)) {
286 return luaL_error(L, "Unable to find function '%s'", fname);
287 }
288 } else {
289 lua_getfield(L, LUA_REGISTRYINDEX, "collectd:callback_num");
290 int tmp = lua_tointeger(L, -1);
291 ssnprintf(subname, sizeof(subname), "callback_%d", tmp);
292 lua_pop(L, 1); // Remove old value from stack
293 lua_pushinteger(L, tmp + 1);
294 lua_setfield(L, LUA_REGISTRYINDEX, "collectd:callback_num"); // pops value
295 }
296
297 luaL_checktype(L, 1, LUA_TFUNCTION);
298
299 lua_getfield(L, LUA_REGISTRYINDEX, "collectd:script_path");
300 char function_name[DATA_MAX_NAME_LEN];
301 ssnprintf(function_name, sizeof(function_name), "lua/%s/%s",
302 lua_tostring(L, -1), subname);
303 lua_pop(L, 1);
304
305 int callback_id = clua_store_callback(L, 1);
306 if (callback_id < 0)
307 return luaL_error(L, "%s", "Storing callback function failed");
308
309 lua_State *thread = lua_newthread(L);
310 if (thread == NULL)
311 return luaL_error(L, "%s", "lua_newthread failed");
312 clua_store_thread(L, -1);
313 lua_pop(L, 1);
314
315 clua_callback_data_t *cb = calloc(1, sizeof(*cb));
316 if (cb == NULL)
317 return luaL_error(L, "%s", "calloc failed");
318
319 cb->lua_state = thread;
320 cb->callback_id = callback_id;
321 cb->lua_function_name = strdup(function_name);
322 pthread_mutex_init(&cb->lock, NULL);
323
324 if (PLUGIN_READ == type) {
325 int status = plugin_register_complex_read(/* group = */ "lua",
326 /* name = */ function_name,
327 /* callback = */ clua_read,
328 /* interval = */ 0,
329 &(user_data_t){
330 .data = cb,
331 .free_func = lua_cb_free,
332 });
333
334 if (status != 0)
335 return luaL_error(L, "%s", "plugin_register_complex_read failed");
336 return 0;
337 } else if (PLUGIN_WRITE == type) {
338 int status = plugin_register_write(/* name = */ function_name,
339 /* callback = */ clua_write,
340 &(user_data_t){
341 .data = cb,
342 .free_func = lua_cb_free,
343 });
344
345 if (status != 0)
346 return luaL_error(L, "%s", "plugin_register_write failed");
347 return 0;
348 } else {
349 return luaL_error(L, "%s", "lua_cb_register_generic unsupported type");
350 }
351 } /* }}} int lua_cb_register_generic */
352
lua_cb_register_read(lua_State * L)353 static int lua_cb_register_read(lua_State *L) {
354 return lua_cb_register_generic(L, PLUGIN_READ);
355 }
356
lua_cb_register_write(lua_State * L)357 static int lua_cb_register_write(lua_State *L) {
358 return lua_cb_register_generic(L, PLUGIN_WRITE);
359 }
360
361 static const luaL_Reg collectdlib[] = {
362 {"log_debug", lua_cb_log_debug},
363 {"log_error", lua_cb_log_error},
364 {"log_info", lua_cb_log_info},
365 {"log_notice", lua_cb_log_notice},
366 {"log_warning", lua_cb_log_warning},
367 {"dispatch_values", lua_cb_dispatch_values},
368 {"register_read", lua_cb_register_read},
369 {"register_write", lua_cb_register_write},
370 {NULL, NULL}};
371
open_collectd(lua_State * L)372 static int open_collectd(lua_State *L) /* {{{ */
373 {
374 #if LUA_VERSION_NUM < 502
375 luaL_register(L, "collectd", collectdlib);
376 #else
377 luaL_newlib(L, collectdlib);
378 #endif
379 return 1;
380 } /* }}} */
381
lua_script_free(lua_script_t * script)382 static void lua_script_free(lua_script_t *script) /* {{{ */
383 {
384 if (script == NULL)
385 return;
386
387 lua_script_t *next = script->next;
388
389 if (script->lua_state != NULL) {
390 lua_close(script->lua_state);
391 script->lua_state = NULL;
392 }
393
394 sfree(script);
395
396 lua_script_free(next);
397 } /* }}} void lua_script_free */
398
lua_script_init(lua_script_t * script)399 static int lua_script_init(lua_script_t *script) /* {{{ */
400 {
401 memset(script, 0, sizeof(*script));
402
403 /* initialize the lua context */
404 script->lua_state = luaL_newstate();
405 if (script->lua_state == NULL) {
406 ERROR("Lua plugin: luaL_newstate() failed.");
407 return -1;
408 }
409
410 /* Open up all the standard Lua libraries. */
411 luaL_openlibs(script->lua_state);
412
413 /* Load the 'collectd' library */
414 #if LUA_VERSION_NUM < 502
415 lua_pushcfunction(script->lua_state, open_collectd);
416 lua_pushstring(script->lua_state, "collectd");
417 lua_call(script->lua_state, 1, 0);
418 #else
419 luaL_requiref(script->lua_state, "collectd", open_collectd, 1);
420 lua_pop(script->lua_state, 1);
421 #endif
422
423 /* Prepend BasePath to package.path */
424 if (base_path[0] != '\0') {
425 lua_getglobal(script->lua_state, "package");
426 lua_getfield(script->lua_state, -1, "path");
427
428 const char *cur_path = lua_tostring(script->lua_state, -1);
429 char *new_path = ssnprintf_alloc("%s/?.lua;%s", base_path, cur_path);
430
431 lua_pop(script->lua_state, 1);
432 lua_pushstring(script->lua_state, new_path);
433
434 free(new_path);
435
436 lua_setfield(script->lua_state, -2, "path");
437 lua_pop(script->lua_state, 1);
438 }
439
440 return 0;
441 } /* }}} int lua_script_init */
442
lua_script_load(const char * script_path)443 static int lua_script_load(const char *script_path) /* {{{ */
444 {
445 lua_script_t *script = malloc(sizeof(*script));
446 if (script == NULL) {
447 ERROR("Lua plugin: malloc failed.");
448 return -1;
449 }
450
451 int status = lua_script_init(script);
452 if (status != 0) {
453 lua_script_free(script);
454 return status;
455 }
456
457 status = luaL_loadfile(script->lua_state, script_path);
458 if (status != 0) {
459 ERROR("Lua plugin: luaL_loadfile failed: %s",
460 lua_tostring(script->lua_state, -1));
461 lua_pop(script->lua_state, 1);
462 lua_script_free(script);
463 return -1;
464 }
465
466 lua_pushstring(script->lua_state, script_path);
467 lua_setfield(script->lua_state, LUA_REGISTRYINDEX, "collectd:script_path");
468 lua_pushinteger(script->lua_state, 0);
469 lua_setfield(script->lua_state, LUA_REGISTRYINDEX, "collectd:callback_num");
470
471 status = lua_pcall(script->lua_state,
472 /* nargs = */ 0,
473 /* nresults = */ LUA_MULTRET,
474 /* errfunc = */ 0);
475 if (status != 0) {
476 const char *errmsg = lua_tostring(script->lua_state, -1);
477
478 if (errmsg == NULL)
479 ERROR("Lua plugin: lua_pcall failed with status %i. "
480 "In addition, no error message could be retrieved from the stack.",
481 status);
482 else
483 ERROR("Lua plugin: Executing script \"%s\" failed: %s", script_path,
484 errmsg);
485 }
486
487 /* Append this script to the global list of scripts. */
488 if (scripts) {
489 lua_script_t *last = scripts;
490 while (last->next)
491 last = last->next;
492
493 last->next = script;
494 } else {
495 scripts = script;
496 }
497
498 if (status != 0)
499 return -1;
500
501 return 0;
502 } /* }}} int lua_script_load */
503
lua_config_base_path(const oconfig_item_t * ci)504 static int lua_config_base_path(const oconfig_item_t *ci) /* {{{ */
505 {
506 int status = cf_util_get_string_buffer(ci, base_path, sizeof(base_path));
507 if (status != 0)
508 return status;
509
510 size_t len = strlen(base_path);
511 while ((len > 0) && (base_path[len - 1] == '/')) {
512 len--;
513 base_path[len] = '\0';
514 }
515
516 DEBUG("Lua plugin: base_path = \"%s\";", base_path);
517
518 return 0;
519 } /* }}} int lua_config_base_path */
520
lua_config_script(const oconfig_item_t * ci)521 static int lua_config_script(const oconfig_item_t *ci) /* {{{ */
522 {
523 char rel_path[PATH_MAX];
524
525 int status = cf_util_get_string_buffer(ci, rel_path, sizeof(rel_path));
526 if (status != 0)
527 return status;
528
529 char abs_path[PATH_MAX];
530
531 if (base_path[0] == '\0')
532 sstrncpy(abs_path, rel_path, sizeof(abs_path));
533 else
534 ssnprintf(abs_path, sizeof(abs_path), "%s/%s", base_path, rel_path);
535
536 DEBUG("Lua plugin: abs_path = \"%s\";", abs_path);
537
538 status = lua_script_load(abs_path);
539 if (status != 0)
540 return status;
541
542 INFO("Lua plugin: File \"%s\" loaded successfully", abs_path);
543
544 return 0;
545 } /* }}} int lua_config_script */
546
547 /*
548 * <Plugin lua>
549 * BasePath "/"
550 * Script "script1.lua"
551 * Script "script2.lua"
552 * </Plugin>
553 */
lua_config(oconfig_item_t * ci)554 static int lua_config(oconfig_item_t *ci) /* {{{ */
555 {
556 int status = 0;
557 for (int i = 0; i < ci->children_num; i++) {
558 oconfig_item_t *child = ci->children + i;
559
560 if (strcasecmp("BasePath", child->key) == 0) {
561 status = lua_config_base_path(child);
562 } else if (strcasecmp("Script", child->key) == 0) {
563 status = lua_config_script(child);
564 } else {
565 ERROR("Lua plugin: Option `%s' is not allowed here.", child->key);
566 status = 1;
567 }
568 }
569
570 return status;
571 } /* }}} int lua_config */
572
lua_shutdown(void)573 static int lua_shutdown(void) /* {{{ */
574 {
575 lua_script_free(scripts);
576
577 return 0;
578 } /* }}} int lua_shutdown */
579
module_register(void)580 void module_register(void) {
581 plugin_register_complex_config("lua", lua_config);
582 plugin_register_shutdown("lua", lua_shutdown);
583 }
584