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