Merge branch 'collectd-5.5' into collectd-5.6
[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 /* <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
34
35 #include "collectd.h"
36 #include "common.h"
37 #include "plugin.h"
38
39 /* Include the Lua API header files. */
40 #include "utils_lua.h"
41 #include <lauxlib.h>
42 #include <lua.h>
43 #include <lualib.h>
44
45 #include <pthread.h>
46
47 #if COLLECT_DEBUG && __GNUC__
48 #undef sprintf
49 #pragma GCC poison sprintf
50 #endif
51
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;
57
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;
64
65 static char base_path[PATH_MAX];
66 static lua_script_t *scripts;
67
68 static int clua_store_callback(lua_State *L, int idx) /* {{{ */
69 {
70   /* Copy the function pointer */
71   lua_pushvalue(L, idx);
72
73   return luaL_ref(L, LUA_REGISTRYINDEX);
74 } /* }}} int clua_store_callback */
75
76 static int clua_load_callback(lua_State *L, int callback_ref) /* {{{ */
77 {
78   lua_rawgeti(L, LUA_REGISTRYINDEX, callback_ref);
79
80   if (!lua_isfunction(L, -1)) {
81     lua_pop(L, 1);
82     return (-1);
83   }
84
85   return (0);
86 } /* }}} int clua_load_callback */
87
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;
94
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   }
101
102   luaL_ref(L, LUA_REGISTRYINDEX);
103   lua_pop(L, 1); /* -1 = 0 */
104   return (0);
105 } /* }}} int clua_store_thread */
106
107 static int clua_read(user_data_t *ud) /* {{{ */
108 {
109   clua_callback_data_t *cb = ud->data;
110
111   pthread_mutex_lock(&cb->lock);
112
113   lua_State *L = cb->lua_state;
114
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 */
123
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   }
136
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   }
145
146   /* pop return value and function */
147   lua_pop(L, 1); /* -1 = 0 */
148
149   pthread_mutex_unlock(&cb->lock);
150   return (status);
151 } /* }}} int clua_read */
152
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;
156
157   pthread_mutex_lock(&cb->lock);
158
159   lua_State *L = cb->lua_state;
160
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 */
169
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 */
178
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   }
191
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   }
200
201   lua_pop(L, 1); /* -1 = 0 */
202   pthread_mutex_unlock(&cb->lock);
203   return (status);
204 } /* }}} int clua_write */
205
206 /*
207  * Exported functions
208  */
209
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 */
216
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 */
223
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 */
230
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 */
237
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 */
244
245 static int lua_cb_dispatch_values(lua_State *L) /* {{{ */
246 {
247   int nargs = lua_gettop(L);
248
249   if (nargs != 1)
250     return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
251
252   luaL_checktype(L, 1, LUA_TTABLE);
253
254   value_list_t *vl = luaC_tovaluelist(L, -1);
255   if (vl == NULL)
256     return luaL_error(L, "%s", "luaC_tovaluelist failed");
257
258 #if COLLECT_DEBUG
259   char identifier[6 * DATA_MAX_NAME_LEN];
260   FORMAT_VL(identifier, sizeof(identifier), vl);
261
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
267
268   plugin_dispatch_values(vl);
269
270   sfree(vl->values);
271   sfree(vl);
272   return 0;
273 } /* }}} lua_cb_dispatch_values */
274
275 static int lua_cb_register_read(lua_State *L) /* {{{ */
276 {
277   int nargs = lua_gettop(L);
278
279   if (nargs != 1)
280     return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
281
282   luaL_checktype(L, 1, LUA_TFUNCTION);
283
284   char function_name[DATA_MAX_NAME_LEN];
285   ssnprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
286
287   int callback_id = clua_store_callback(L, 1);
288   if (callback_id < 0)
289     return luaL_error(L, "%s", "Storing callback function failed");
290
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);
296
297   clua_callback_data_t *cb = calloc(1, sizeof(*cb));
298   if (cb == NULL)
299     return luaL_error(L, "%s", "calloc failed");
300
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);
305
306   user_data_t ud = {
307     .data = cb
308   };
309
310   int status = plugin_register_complex_read(/* group = */ "lua",
311                                             /* name      = */ function_name,
312                                             /* callback  = */ clua_read,
313                                             /* interval  = */ 0,
314                                             /* user_data = */ &ud);
315
316   if (status != 0)
317     return luaL_error(L, "%s", "plugin_register_complex_read failed");
318   return 0;
319 } /* }}} int lua_cb_register_read */
320
321 static int lua_cb_register_write(lua_State *L) /* {{{ */
322 {
323   int nargs = lua_gettop(L);
324
325   if (nargs != 1)
326     return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
327
328   luaL_checktype(L, 1, LUA_TFUNCTION);
329
330   char function_name[DATA_MAX_NAME_LEN] = "";
331   ssnprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
332
333   int callback_id = clua_store_callback(L, 1);
334   if (callback_id < 0)
335     return luaL_error(L, "%s", "Storing callback function failed");
336
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);
342
343   clua_callback_data_t *cb = calloc(1, sizeof(*cb));
344   if (cb == NULL)
345     return luaL_error(L, "%s", "calloc failed");
346
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);
351
352   user_data_t ud = {
353     .data = cb
354   };
355
356   int status = plugin_register_write(/* name = */ function_name,
357                                     /* callback  = */ clua_write,
358                                     /* user_data = */ &ud);
359
360   if (status != 0)
361     return luaL_error(L, "%s", "plugin_register_write failed");
362   return 0;
363 } /* }}} int lua_cb_register_write */
364
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 };
376
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 } /* }}} */
386
387 static void lua_script_free(lua_script_t *script) /* {{{ */
388 {
389   if (script == NULL)
390     return;
391
392   lua_script_t *next = script->next;
393
394   if (script->lua_state != NULL) {
395     lua_close(script->lua_state);
396     script->lua_state = NULL;
397   }
398
399   sfree(script->script_path);
400   sfree(script);
401
402   lua_script_free(next);
403 } /* }}} void lua_script_free */
404
405 static int lua_script_init(lua_script_t *script) /* {{{ */
406 {
407   memset(script, 0, sizeof(*script));
408
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   }
415
416   /* Open up all the standard Lua libraries. */
417   luaL_openlibs(script->lua_state);
418
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
428
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");
433
434     const char *cur_path = lua_tostring(script->lua_state, -1);
435     char *new_path = ssnprintf_alloc("%s/?.lua;%s", base_path, cur_path);
436
437     lua_pop(script->lua_state, 1);
438     lua_pushstring(script->lua_state, new_path);
439
440     free(new_path);
441
442     lua_setfield(script->lua_state, -2, "path");
443     lua_pop(script->lua_state, 1);
444   }
445
446   return (0);
447 } /* }}} int lua_script_init */
448
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   }
456
457   int status = lua_script_init(script);
458   if (status != 0) {
459     lua_script_free(script);
460     return (status);
461   }
462
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   }
469
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   }
478
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);
485
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);
493
494     lua_script_free(script);
495     return (-1);
496   }
497
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;
503
504     last->next = script;
505   } else {
506     scripts = script;
507   }
508
509   return (0);
510 } /* }}} int lua_script_load */
511
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);
517
518   size_t len = strlen(base_path);
519   while ((len > 0) && (base_path[len - 1] == '/')) {
520     len--;
521     base_path[len] = '\0';
522   }
523
524   DEBUG("Lua plugin: base_path = \"%s\";", base_path);
525
526   return (0);
527 } /* }}} int lua_config_base_path */
528
529 static int lua_config_script(const oconfig_item_t *ci) /* {{{ */
530 {
531   char rel_path[PATH_MAX];
532
533   int status = cf_util_get_string_buffer(ci, rel_path, sizeof(rel_path));
534   if (status != 0)
535     return (status);
536
537   char abs_path[PATH_MAX];
538
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);
543
544   DEBUG("Lua plugin: abs_path = \"%s\";", abs_path);
545
546   status = lua_script_load(abs_path);
547   if (status != 0)
548     return (status);
549
550   INFO("Lua plugin: File \"%s\" loaded successfully", abs_path);
551
552   return 0;
553 } /* }}} int lua_config_script */
554
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;
567
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   }
577
578   return status;
579 } /* }}} int lua_config */
580
581 static int lua_shutdown(void) /* {{{ */
582 {
583   lua_script_free(scripts);
584
585   return (0);
586 } /* }}} int lua_shutdown */
587
588 void module_register(void) {
589   plugin_register_complex_config("lua", lua_config);
590   plugin_register_shutdown("lua", lua_shutdown);
591 }
592
593 /* vim: set sw=2 sts=2 et fdm=marker : */