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