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