b89c824001f6bffc1770ae80914a8145e7f9484d
[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 typedef struct {
66   const char *name;
67   lua_CFunction func;
68 } lua_c_function_t;
69
70 static char base_path[PATH_MAX];
71 static lua_script_t *scripts;
72
73 static int clua_store_callback(lua_State *L, int idx) /* {{{ */
74 {
75   /* Copy the function pointer */
76   lua_pushvalue(L, idx);
77
78   return luaL_ref(L, LUA_REGISTRYINDEX);
79 } /* }}} int clua_store_callback */
80
81 static int clua_load_callback(lua_State *L, int callback_ref) /* {{{ */
82 {
83   lua_rawgeti(L, LUA_REGISTRYINDEX, callback_ref);
84
85   if (!lua_isfunction(L, -1)) {
86     lua_pop(L, 1);
87     return (-1);
88   }
89
90   return (0);
91 } /* }}} int clua_load_callback */
92
93 /* Store the threads in a global variable so they are not cleaned up by the
94  * garbage collector. */
95 static int clua_store_thread(lua_State *L, int idx) /* {{{ */
96 {
97   if (idx < 0)
98     idx += lua_gettop(L) + 1;
99
100   /* Copy the thread pointer */
101   lua_pushvalue(L, idx); /* +1 = 3 */
102   if (!lua_isthread(L, -1)) {
103     lua_pop(L, 3); /* -3 = 0 */
104     return (-1);
105   }
106
107   luaL_ref(L, LUA_REGISTRYINDEX);
108   lua_pop(L, 1); /* -1 = 0 */
109   return (0);
110 } /* }}} int clua_store_thread */
111
112 static int clua_read(user_data_t *ud) /* {{{ */
113 {
114   clua_callback_data_t *cb = ud->data;
115
116   pthread_mutex_lock(&cb->lock);
117
118   lua_State *L = cb->lua_state;
119
120   int status = clua_load_callback(L, cb->callback_id);
121   if (status != 0) {
122     ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).",
123           cb->lua_function_name, cb->callback_id);
124     pthread_mutex_unlock(&cb->lock);
125     return (-1);
126   }
127   /* +1 = 1 */
128
129   status = lua_pcall(L, 0, 1, 0);
130   if (status != 0) {
131     const char *errmsg = lua_tostring(L, -1);
132     if (errmsg == NULL)
133       ERROR("Lua plugin: Calling a read callback failed. "
134             "In addition, retrieving the error message failed.");
135     else
136       ERROR("Lua plugin: Calling a read callback failed: %s", errmsg);
137     lua_pop(L, 1);
138     pthread_mutex_unlock(&cb->lock);
139     return (-1);
140   }
141
142   if (!lua_isnumber(L, -1)) {
143     ERROR("Lua plugin: Read function \"%s\" (id %i) did not return a numeric "
144           "status.",
145           cb->lua_function_name, cb->callback_id);
146     status = -1;
147   } else {
148     status = (int)lua_tointeger(L, -1);
149   }
150
151   /* pop return value and function */
152   lua_pop(L, 1); /* -1 = 0 */
153
154   pthread_mutex_unlock(&cb->lock);
155   return (status);
156 } /* }}} int clua_read */
157
158 static int clua_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */
159                       user_data_t *ud) {
160   clua_callback_data_t *cb = ud->data;
161
162   pthread_mutex_lock(&cb->lock);
163
164   lua_State *L = cb->lua_state;
165
166   int status = clua_load_callback(L, cb->callback_id);
167   if (status != 0) {
168     ERROR("Lua plugin: Unable to load callback \"%s\" (id %i).",
169           cb->lua_function_name, cb->callback_id);
170     pthread_mutex_unlock(&cb->lock);
171     return (-1);
172   }
173   /* +1 = 1 */
174
175   status = luaC_pushvaluelist(L, ds, vl);
176   if (status != 0) {
177     lua_pop(L, 1); /* -1 = 0 */
178     pthread_mutex_unlock(&cb->lock);
179     ERROR("Lua plugin: luaC_pushvaluelist failed.");
180     return (-1);
181   }
182   /* +1 = 2 */
183
184   status = lua_pcall(L, 1, 1, 0); /* -2+1 = 1 */
185   if (status != 0) {
186     const char *errmsg = lua_tostring(L, -1);
187     if (errmsg == NULL)
188       ERROR("Lua plugin: Calling the write callback failed. "
189             "In addition, retrieving the error message failed.");
190     else
191       ERROR("Lua plugin: Calling the write callback failed:\n%s", errmsg);
192     lua_pop(L, 1); /* -1 = 0 */
193     pthread_mutex_unlock(&cb->lock);
194     return (-1);
195   }
196
197   if (!lua_isnumber(L, -1)) {
198     ERROR("Lua plugin: Write function \"%s\" (id %i) did not return a numeric "
199           "value.",
200           cb->lua_function_name, cb->callback_id);
201     status = -1;
202   } else {
203     status = (int)lua_tointeger(L, -1);
204   }
205
206   lua_pop(L, 1); /* -1 = 0 */
207   pthread_mutex_unlock(&cb->lock);
208   return (status);
209 } /* }}} int clua_write */
210
211 /*
212  * Exported functions
213  */
214
215 static int lua_cb_log_debug(lua_State *L) /* {{{ */
216 {
217   const char *msg = luaL_checkstring(L, 1);
218   plugin_log(LOG_DEBUG, "%s", msg);
219   return 0;
220 } /* }}} int lua_cb_log_debug */
221
222 static int lua_cb_log_error(lua_State *L) /* {{{ */
223 {
224   const char *msg = luaL_checkstring(L, 1);
225   plugin_log(LOG_ERR, "%s", msg);
226   return 0;
227 } /* }}} int lua_cb_log_error */
228
229 static int lua_cb_log_info(lua_State *L) /* {{{ */
230 {
231   const char *msg = luaL_checkstring(L, 1);
232   plugin_log(LOG_INFO, "%s", msg);
233   return 0;
234 } /* }}} int lua_cb_log_info */
235
236 static int lua_cb_log_notice(lua_State *L) /* {{{ */
237 {
238   const char *msg = luaL_checkstring(L, 1);
239   plugin_log(LOG_NOTICE, "%s", msg);
240   return 0;
241 } /* }}} int lua_cb_log_notice */
242
243 static int lua_cb_log_warning(lua_State *L) /* {{{ */
244 {
245   const char *msg = luaL_checkstring(L, 1);
246   plugin_log(LOG_WARNING, "%s", msg);
247   return 0;
248 } /* }}} int lua_cb_log_warning */
249
250 static int lua_cb_dispatch_values(lua_State *L) /* {{{ */
251 {
252   int nargs = lua_gettop(L);
253
254   if (nargs != 1)
255     return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
256
257   luaL_checktype(L, 1, LUA_TTABLE);
258
259   value_list_t *vl = luaC_tovaluelist(L, -1);
260   if (vl == NULL)
261     return luaL_error(L, "%s", "luaC_tovaluelist failed");
262
263 #if COLLECT_DEBUG
264   char identifier[6 * DATA_MAX_NAME_LEN];
265   FORMAT_VL(identifier, sizeof(identifier), vl);
266
267   DEBUG("Lua plugin: collectd.dispatch_values(): Received value list \"%s\", "
268         "time %.3f, interval %.3f.",
269         identifier, CDTIME_T_TO_DOUBLE(vl->time),
270         CDTIME_T_TO_DOUBLE(vl->interval));
271 #endif
272
273   plugin_dispatch_values(vl);
274
275   sfree(vl->values);
276   sfree(vl);
277   return 0;
278 } /* }}} lua_cb_dispatch_values */
279
280 static int lua_cb_register_read(lua_State *L) /* {{{ */
281 {
282   int nargs = lua_gettop(L);
283
284   if (nargs != 1)
285     return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
286
287   luaL_checktype(L, 1, LUA_TFUNCTION);
288
289   char function_name[DATA_MAX_NAME_LEN];
290   ssnprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
291
292   int callback_id = clua_store_callback(L, 1);
293   if (callback_id < 0)
294     return luaL_error(L, "%s", "Storing callback function failed");
295
296   lua_State *thread = lua_newthread(L);
297   if (thread == NULL)
298     return luaL_error(L, "%s", "lua_newthread failed");
299   clua_store_thread(L, -1);
300   lua_pop(L, 1);
301
302   clua_callback_data_t *cb = calloc(1, sizeof(*cb));
303   if (cb == NULL)
304     return luaL_error(L, "%s", "calloc failed");
305
306   cb->lua_state = thread;
307   cb->callback_id = callback_id;
308   cb->lua_function_name = strdup(function_name);
309   pthread_mutex_init(&cb->lock, NULL);
310
311   user_data_t ud = {
312     .data = cb
313   };
314
315   int status = plugin_register_complex_read(/* group = */ "lua",
316                                             /* name      = */ function_name,
317                                             /* callback  = */ clua_read,
318                                             /* interval  = */ 0,
319                                             /* user_data = */ &ud);
320
321   if (status != 0)
322     return luaL_error(L, "%s", "plugin_register_complex_read failed");
323   return 0;
324 } /* }}} int lua_cb_register_read */
325
326 static int lua_cb_register_write(lua_State *L) /* {{{ */
327 {
328   int nargs = lua_gettop(L);
329
330   if (nargs != 1)
331     return luaL_error(L, "Invalid number of arguments (%d != 1)", nargs);
332
333   luaL_checktype(L, 1, LUA_TFUNCTION);
334
335   char function_name[DATA_MAX_NAME_LEN] = "";
336   ssnprintf(function_name, sizeof(function_name), "lua/%s", lua_tostring(L, 1));
337
338   int callback_id = clua_store_callback(L, 1);
339   if (callback_id < 0)
340     return luaL_error(L, "%s", "Storing callback function failed");
341
342   lua_State *thread = lua_newthread(L);
343   if (thread == NULL)
344     return luaL_error(L, "%s", "lua_newthread failed");
345   clua_store_thread(L, -1);
346   lua_pop(L, 1);
347
348   clua_callback_data_t *cb = calloc(1, sizeof(*cb));
349   if (cb == NULL)
350     return luaL_error(L, "%s", "calloc failed");
351
352   cb->lua_state = thread;
353   cb->callback_id = callback_id;
354   cb->lua_function_name = strdup(function_name);
355   pthread_mutex_init(&cb->lock, NULL);
356
357   user_data_t ud = {
358     .data = cb
359   };
360
361   int status = plugin_register_write(/* name = */ function_name,
362                                     /* callback  = */ clua_write,
363                                     /* user_data = */ &ud);
364
365   if (status != 0)
366     return luaL_error(L, "%s", "plugin_register_write failed");
367   return 0;
368 } /* }}} int lua_cb_register_write */
369
370 static lua_c_function_t lua_c_functions[] = {
371     {"log_debug", lua_cb_log_debug},
372     {"log_error", lua_cb_log_error},
373     {"log_info", lua_cb_log_info},
374     {"log_notice", lua_cb_log_notice},
375     {"log_warning", lua_cb_log_warning},
376     {"dispatch_values", lua_cb_dispatch_values},
377     {"register_read", lua_cb_register_read},
378     {"register_write", lua_cb_register_write}};
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   /* Register all the functions we implement in C */
413   lua_newtable(script->lua_state);
414   for (size_t i = 0; i < STATIC_ARRAY_SIZE(lua_c_functions); i++) {
415     lua_pushcfunction(script->lua_state, lua_c_functions[i].func);
416     lua_setfield(script->lua_state, -2, lua_c_functions[i].name);
417   }
418   lua_setglobal(script->lua_state, "collectd");
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   script->script_path = strdup(script_path);
455   if (script->script_path == NULL) {
456     ERROR("Lua plugin: strdup failed.");
457     lua_script_free(script);
458     return (-1);
459   }
460
461   status = luaL_loadfile(script->lua_state, script->script_path);
462   if (status != 0) {
463     ERROR("Lua plugin: luaL_loadfile failed: %s",
464           lua_tostring(script->lua_state, -1));
465     lua_pop(script->lua_state, 1);
466     lua_script_free(script);
467     return (-1);
468   }
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:\n%s",
483             script->script_path, errmsg);
484
485     lua_script_free(script);
486     return (-1);
487   }
488
489   /* Append this script to the global list of scripts. */
490   if (scripts) {
491     lua_script_t *last = scripts;
492     while (last->next)
493       last = last->next;
494
495     last->next = script;
496   } else {
497     scripts = script;
498   }
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 succesfully", 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() {
580   plugin_register_complex_config("lua", lua_config);
581   plugin_register_shutdown("lua", lua_shutdown);
582 }
583
584 /* vim: set sw=2 sts=2 et fdm=marker : */