Add snprintf wrapper for GCC 8.2/3
[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 #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
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
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. */
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
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
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
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
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
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
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
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
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
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
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 =
326         plugin_register_complex_read(/* group = */ "lua",
327                                      /* name      = */ function_name,
328                                      /* callback  = */ clua_read,
329                                      /* interval  = */ 0,
330                                      &(user_data_t){
331                                          .data = cb, .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, .free_func = lua_cb_free,
342                                        });
343
344     if (status != 0)
345       return luaL_error(L, "%s", "plugin_register_write failed");
346     return 0;
347   } else {
348     return luaL_error(L, "%s", "lua_cb_register_generic unsupported type");
349   }
350 } /* }}} int lua_cb_register_generic */
351
352 static int lua_cb_register_read(lua_State *L) {
353   return lua_cb_register_generic(L, PLUGIN_READ);
354 }
355
356 static int lua_cb_register_write(lua_State *L) {
357   return lua_cb_register_generic(L, PLUGIN_WRITE);
358 }
359
360 static const luaL_Reg collectdlib[] = {
361     {"log_debug", lua_cb_log_debug},
362     {"log_error", lua_cb_log_error},
363     {"log_info", lua_cb_log_info},
364     {"log_notice", lua_cb_log_notice},
365     {"log_warning", lua_cb_log_warning},
366     {"dispatch_values", lua_cb_dispatch_values},
367     {"register_read", lua_cb_register_read},
368     {"register_write", lua_cb_register_write},
369     {NULL, NULL}};
370
371 static int open_collectd(lua_State *L) /* {{{ */
372 {
373 #if LUA_VERSION_NUM < 502
374   luaL_register(L, "collectd", collectdlib);
375 #else
376   luaL_newlib(L, collectdlib);
377 #endif
378   return 1;
379 } /* }}} */
380
381 static void lua_script_free(lua_script_t *script) /* {{{ */
382 {
383   if (script == NULL)
384     return;
385
386   lua_script_t *next = script->next;
387
388   if (script->lua_state != NULL) {
389     lua_close(script->lua_state);
390     script->lua_state = NULL;
391   }
392
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   status = luaL_loadfile(script->lua_state, script_path);
457   if (status != 0) {
458     ERROR("Lua plugin: luaL_loadfile failed: %s",
459           lua_tostring(script->lua_state, -1));
460     lua_pop(script->lua_state, 1);
461     lua_script_free(script);
462     return -1;
463   }
464
465   lua_pushstring(script->lua_state, script_path);
466   lua_setfield(script->lua_state, LUA_REGISTRYINDEX, "collectd:script_path");
467   lua_pushinteger(script->lua_state, 0);
468   lua_setfield(script->lua_state, LUA_REGISTRYINDEX, "collectd:callback_num");
469
470   status = lua_pcall(script->lua_state,
471                      /* nargs = */ 0,
472                      /* nresults = */ LUA_MULTRET,
473                      /* errfunc = */ 0);
474   if (status != 0) {
475     const char *errmsg = lua_tostring(script->lua_state, -1);
476
477     if (errmsg == NULL)
478       ERROR("Lua plugin: lua_pcall failed with status %i. "
479             "In addition, no error message could be retrieved from the stack.",
480             status);
481     else
482       ERROR("Lua plugin: Executing script \"%s\" failed: %s", script_path,
483             errmsg);
484   }
485
486   /* Append this script to the global list of scripts. */
487   if (scripts) {
488     lua_script_t *last = scripts;
489     while (last->next)
490       last = last->next;
491
492     last->next = script;
493   } else {
494     scripts = script;
495   }
496
497   if (status != 0)
498     return -1;
499
500   return 0;
501 } /* }}} int lua_script_load */
502
503 static int lua_config_base_path(const oconfig_item_t *ci) /* {{{ */
504 {
505   int status = cf_util_get_string_buffer(ci, base_path, sizeof(base_path));
506   if (status != 0)
507     return status;
508
509   size_t len = strlen(base_path);
510   while ((len > 0) && (base_path[len - 1] == '/')) {
511     len--;
512     base_path[len] = '\0';
513   }
514
515   DEBUG("Lua plugin: base_path = \"%s\";", base_path);
516
517   return 0;
518 } /* }}} int lua_config_base_path */
519
520 static int lua_config_script(const oconfig_item_t *ci) /* {{{ */
521 {
522   char rel_path[PATH_MAX];
523
524   int status = cf_util_get_string_buffer(ci, rel_path, sizeof(rel_path));
525   if (status != 0)
526     return status;
527
528   char abs_path[PATH_MAX];
529
530   if (base_path[0] == '\0')
531     sstrncpy(abs_path, rel_path, sizeof(abs_path));
532   else
533     ssnprintf(abs_path, sizeof(abs_path), "%s/%s", base_path, rel_path);
534
535   DEBUG("Lua plugin: abs_path = \"%s\";", abs_path);
536
537   status = lua_script_load(abs_path);
538   if (status != 0)
539     return status;
540
541   INFO("Lua plugin: File \"%s\" loaded successfully", abs_path);
542
543   return 0;
544 } /* }}} int lua_config_script */
545
546 /*
547  * <Plugin lua>
548  *   BasePath "/"
549  *   Script "script1.lua"
550  *   Script "script2.lua"
551  * </Plugin>
552  */
553 static int lua_config(oconfig_item_t *ci) /* {{{ */
554 {
555   int status = 0;
556   for (int i = 0; i < ci->children_num; i++) {
557     oconfig_item_t *child = ci->children + i;
558
559     if (strcasecmp("BasePath", child->key) == 0) {
560       status = lua_config_base_path(child);
561     } else if (strcasecmp("Script", child->key) == 0) {
562       status = lua_config_script(child);
563     } else {
564       ERROR("Lua plugin: Option `%s' is not allowed here.", child->key);
565       status = 1;
566     }
567   }
568
569   return status;
570 } /* }}} int lua_config */
571
572 static int lua_shutdown(void) /* {{{ */
573 {
574   lua_script_free(scripts);
575
576   return 0;
577 } /* }}} int lua_shutdown */
578
579 void module_register(void) {
580   plugin_register_complex_config("lua", lua_config);
581   plugin_register_shutdown("lua", lua_shutdown);
582 }