Added a workaround to module loading to allow Python scripts to do imports.
[collectd.git] / src / python.c
1 #include <Python.h>
2 #include <structmember.h>
3
4 #include "collectd.h"
5 #include "common.h"
6
7 typedef struct cpy_callback_s {
8         char *name;
9         PyObject *callback;
10         PyObject *data;
11         struct cpy_callback_s *next;
12 } cpy_callback_t;
13
14 /* This is our global thread state. Python saves some stuff in thread-local
15  * storage. So if we allow the interpreter to run in the background
16  * (the scriptwriters might have created some threads from python), we have
17  * to save the state so we can resume it later after shutdown. */
18
19 static PyThreadState *state;
20
21 /* Serves the same purpose as PyEval_ThreadsInitialized but doesn't require
22  * Python 2.4. */
23  
24 static int cpy_have_threads;
25
26 /* These two macros are basicly Py_BEGIN_ALLOW_THREADS and Py_BEGIN_ALLOW_THREADS
27  * from the other direction. If a Python thread calls a C function
28  * Py_BEGIN_ALLOW_THREADS is used to allow other python threads to run because
29  * we don't intend to call any Python functions.
30  *
31  * These two macros are used whenever a C thread intends to call some Python
32  * function, usually because some registered callback was triggered.
33  * Just like Py_BEGIN_ALLOW_THREADS it opens a block so these macros have to be
34  * used in pairs. They aquire the GIL, create a new Python thread state and swap
35  * the current thread state with the new one. This means this thread is now allowed
36  * to execute Python code. */
37
38 #define CPY_LOCK_THREADS {\
39         PyGILState_STATE gil_state;\
40         if (cpy_have_threads)\
41                 gil_state = PyGILState_Ensure();
42
43 #define CPY_RELEASE_THREADS \
44         if (cpy_have_threads)\
45                 PyGILState_Release(gil_state);\
46 }
47
48
49 static cpy_callback_t *cpy_config_callbacks;
50 static cpy_callback_t *cpy_init_callbacks;
51 static cpy_callback_t *cpy_shutdown_callbacks;
52
53 static void cpy_destroy_user_data(void *data) {
54         cpy_callback_t *c = data;
55         free(c->name);
56         Py_DECREF(c->callback);
57         Py_XDECREF(c->data);
58         free(c);
59 }
60
61 static void cpy_log_callback(int severity, const char *message, user_data_t *data) {
62         cpy_callback_t * c = data->data;
63         PyObject *ret;
64
65         CPY_LOCK_THREADS
66         if (c->data == NULL)
67                 ret = PyObject_CallFunction(c->callback, "is", severity, message); /* New reference. */
68         else
69                 ret = PyObject_CallFunction(c->callback, "isO", severity, message, c->data); /* New reference. */
70
71         if (ret == NULL) {
72                 /* FIXME */
73                 PyErr_Print();
74         } else {
75                 Py_DECREF(ret);
76         }
77         CPY_RELEASE_THREADS
78 }
79
80 typedef struct {
81         PyObject_HEAD      /* No semicolon! */
82         PyObject *parent;
83         PyObject *key;
84         PyObject *values;
85         PyObject *children;
86 } Config;
87
88 static void Config_dealloc(PyObject *s) {
89         Config *self = (Config *) s;
90         
91         Py_XDECREF(self->parent);
92         Py_XDECREF(self->key);
93         Py_XDECREF(self->values);
94         Py_XDECREF(self->children);
95         self->ob_type->tp_free(s);
96 }
97
98 static PyObject *Config_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
99         Config *self;
100         
101         self = (Config *) type->tp_alloc(type, 0);
102         if (self == NULL)
103                 return NULL;
104         
105         self->parent = NULL;
106         self->key = NULL;
107         self->values = NULL;
108         self->children = NULL;
109         return (PyObject *) self;
110 }
111
112 static int Config_init(PyObject *s, PyObject *args, PyObject *kwds) {
113         PyObject *key = NULL, *parent = NULL, *values = NULL, *children = NULL, *tmp;
114         Config *self = (Config *) s;
115         static char *kwlist[] = {"key", "parent", "values", "children", NULL};
116         
117         if (!PyArg_ParseTupleAndKeywords(args, kwds, "S|OOO", kwlist,
118                         &key, &parent, &values, &children))
119                 return -1;
120         
121         if (values == NULL) {
122                 values = PyTuple_New(0);
123                 PyErr_Clear();
124         }
125         if (children == NULL) {
126                 children = PyTuple_New(0);
127                 PyErr_Clear();
128         }
129         tmp = self->key;
130         Py_INCREF(key);
131         self->key = key;
132         Py_XDECREF(tmp);
133         if (parent != NULL) {
134                 tmp = self->parent;
135                 Py_INCREF(parent);
136                 self->parent = parent;
137                 Py_XDECREF(tmp);
138         }
139         if (values != NULL) {
140                 tmp = self->values;
141                 Py_INCREF(values);
142                 self->values = values;
143                 Py_XDECREF(tmp);
144         }
145         if (children != NULL) {
146                 tmp = self->children;
147                 Py_INCREF(children);
148                 self->children = children;
149                 Py_XDECREF(tmp);
150         }
151         return 0;
152 }
153
154 static PyMemberDef Config_members[] = {
155     {"Parent", T_OBJECT, offsetof(Config, parent), 0, "Parent node"},
156     {"Key", T_OBJECT_EX, offsetof(Config, key), 0, "Keyword of this node"},
157     {"Values", T_OBJECT_EX, offsetof(Config, values), 0, "Values after the key"},
158     {"Children", T_OBJECT_EX, offsetof(Config, children), 0, "Childnodes of this node"},
159     {NULL}
160 };
161
162 static PyTypeObject ConfigType = {
163     PyObject_HEAD_INIT(NULL)
164     0,                         /* Always 0 */
165     "collectd.Config",         /* tp_name */
166     sizeof(Config),            /* tp_basicsize */
167     0,                         /* Will be filled in later */
168     Config_dealloc,            /* tp_dealloc */
169     0,                         /* tp_print */
170     0,                         /* tp_getattr */
171     0,                         /* tp_setattr */
172     0,                         /* tp_compare */
173     0,                         /* tp_repr */
174     0,                         /* tp_as_number */
175     0,                         /* tp_as_sequence */
176     0,                         /* tp_as_mapping */
177     0,                         /* tp_hash */
178     0,                         /* tp_call */
179     0,                         /* tp_str */
180     0,                         /* tp_getattro */
181     0,                         /* tp_setattro */
182     0,                         /* tp_as_buffer */
183     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
184     "Cool help text later",    /* tp_doc */
185     0,                         /* tp_traverse */
186     0,                         /* tp_clear */
187     0,                         /* tp_richcompare */
188     0,                         /* tp_weaklistoffset */
189     0,                         /* tp_iter */
190     0,                         /* tp_iternext */
191     0,                         /* tp_methods */
192     Config_members,            /* tp_members */
193     0,                         /* tp_getset */
194     0,                         /* tp_base */
195     0,                         /* tp_dict */
196     0,                         /* tp_descr_get */
197     0,                         /* tp_descr_set */
198     0,                         /* tp_dictoffset */
199     Config_init,               /* tp_init */
200     0,                         /* tp_alloc */
201     Config_new                 /* tp_new */
202 };
203
204 static PyObject *cpy_register_generic(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
205         cpy_callback_t *c;
206         const char *name = NULL;
207         PyObject *callback = NULL, *data = NULL;
208         static char *kwlist[] = {"callback", "data", "name", NULL};
209         
210         if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
211         if (PyCallable_Check(callback) == 0) {
212                 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
213                 return NULL;
214         }
215         if (name == NULL) {
216                 PyObject *mod;
217                 
218                 mod = PyObject_GetAttrString(callback, "__module__");
219                 if (mod != NULL) name = PyString_AsString(mod);
220                 if (name == NULL) {
221                         PyErr_SetString(PyExc_ValueError, "No module name specified and "
222                                 "callback function does not have a \"__module__\" attribute.");
223                         return NULL;
224                 }
225         }
226         Py_INCREF(callback);
227         Py_XINCREF(data);
228         c = malloc(sizeof(*c));
229         c->name = strdup(name);
230         c->callback = callback;
231         c->data = data;
232         c->next = *list_head;
233         *list_head = c;
234         Py_RETURN_NONE;
235 }
236
237 static PyObject *cpy_register_config(PyObject *self, PyObject *args, PyObject *kwds) {
238         return cpy_register_generic(&cpy_config_callbacks, args, kwds);
239 }
240
241 static PyObject *cpy_register_init(PyObject *self, PyObject *args, PyObject *kwds) {
242         return cpy_register_generic(&cpy_init_callbacks, args, kwds);
243 }
244
245 static PyObject *cpy_register_log(PyObject *self, PyObject *args, PyObject *kwds) {
246         cpy_callback_t *c = NULL;
247         user_data_t *user_data = NULL;
248         const char *name = NULL;
249         PyObject *callback = NULL, *data = NULL;
250         static char *kwlist[] = {"callback", "data", "name", NULL};
251         
252         if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
253         if (PyCallable_Check(callback) == 0) {
254                 PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
255                 return NULL;
256         }
257         if (name == NULL) {
258                 PyObject *mod;
259                 
260                 mod = PyObject_GetAttrString(callback, "__module__");
261                 if (mod != NULL) name = PyString_AsString(mod);
262                 if (name == NULL) {
263                         PyErr_SetString(PyExc_ValueError, "No module name specified and "
264                                 "callback function does not have a \"__module__\" attribute.");
265                         return NULL;
266                 }
267         }
268         Py_INCREF(callback);
269         Py_XINCREF(data);
270         c = malloc(sizeof(*c));
271         c->name = strdup(name);
272         c->callback = callback;
273         c->data = data;
274         c->next = NULL;
275         user_data = malloc(sizeof(*user_data));
276         user_data->free_func = cpy_destroy_user_data;
277         user_data->data = c;
278         plugin_register_log(name, cpy_log_callback, user_data);
279         Py_RETURN_NONE;
280 }
281
282 static PyObject *cpy_register_shutdown(PyObject *self, PyObject *args, PyObject *kwds) {
283         return cpy_register_generic(&cpy_shutdown_callbacks, args, kwds);
284 }
285
286 static PyObject *cpy_Error(PyObject *self, PyObject *args) {
287         const char *text;
288         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
289         Py_BEGIN_ALLOW_THREADS
290         plugin_log(LOG_ERR, "%s", text);
291         Py_END_ALLOW_THREADS
292         Py_RETURN_NONE;
293 }
294
295 static PyObject *cpy_Warning(PyObject *self, PyObject *args) {
296         const char *text;
297         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
298         Py_BEGIN_ALLOW_THREADS
299         plugin_log(LOG_WARNING, "%s", text);
300         Py_END_ALLOW_THREADS
301         Py_RETURN_NONE;
302 }
303
304 static PyObject *cpy_Notice(PyObject *self, PyObject *args) {
305         const char *text;
306         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
307         Py_BEGIN_ALLOW_THREADS
308         plugin_log(LOG_NOTICE, "%s", text);
309         Py_END_ALLOW_THREADS
310         Py_RETURN_NONE;
311 }
312
313 static PyObject *cpy_Info(PyObject *self, PyObject *args) {
314         const char *text;
315         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
316         Py_BEGIN_ALLOW_THREADS
317         plugin_log(LOG_INFO, "%s", text);
318         Py_END_ALLOW_THREADS
319         Py_RETURN_NONE;
320 }
321
322 static PyObject *cpy_Debug(PyObject *self, PyObject *args) {
323 #ifdef COLLECT_DEBUG
324         const char *text;
325         if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
326         plugin_log(LOG_DEBUG, "%s", text);
327 #endif
328         Py_RETURN_NONE;
329 }
330
331 static PyMethodDef cpy_methods[] = {
332         {"Debug", cpy_Debug, METH_VARARGS, "This is an unhelpful text."},
333         {"Info", cpy_Info, METH_VARARGS, "This is an unhelpful text."},
334         {"Notice", cpy_Notice, METH_VARARGS, "This is an unhelpful text."},
335         {"Warning", cpy_Warning, METH_VARARGS, "This is an unhelpful text."},
336         {"Error", cpy_Error, METH_VARARGS, "This is an unhelpful text."},
337         {"register_log", (PyCFunction) cpy_register_log, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
338         {"register_init", (PyCFunction) cpy_register_init, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
339         {"register_config", (PyCFunction) cpy_register_config, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
340         {"register_shutdown", (PyCFunction) cpy_register_shutdown, METH_VARARGS | METH_KEYWORDS, "This is an unhelpful text."},
341         {0, 0, 0, 0}
342 };
343
344 static int cpy_shutdown(void) {
345         cpy_callback_t *c;
346         PyObject *ret;
347         
348         /* This can happen if the module was loaded but not configured. */
349         if (state != NULL)
350                 PyEval_RestoreThread(state);
351
352         for (c = cpy_shutdown_callbacks; c; c = c->next) {
353                 if (c->data == NULL)
354                         ret = PyObject_CallObject(c->callback, NULL); /* New reference. */
355                 else
356                         ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
357                 if (ret == NULL)
358                         PyErr_Print(); /* FIXME */
359                 else
360                         Py_DECREF(ret);
361         }
362         Py_Finalize();
363         return 0;
364 }
365
366 static int cpy_init(void) {
367         cpy_callback_t *c;
368         PyObject *ret;
369         
370         PyEval_InitThreads();
371         cpy_have_threads = 1;
372         /* Now it's finally OK to use python threads. */
373         for (c = cpy_init_callbacks; c; c = c->next) {
374                 if (c->data == NULL)
375                         ret = PyObject_CallObject(c->callback, NULL); /* New reference. */
376                 else
377                         ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
378                 if (ret == NULL)
379                         PyErr_Print(); /* FIXME */
380                 else
381                         Py_DECREF(ret);
382         }
383         state = PyEval_SaveThread();
384         return 0;
385 }
386
387 static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
388         int i;
389         PyObject *item, *values, *children, *tmp;
390         
391         if (parent == NULL)
392                 parent = Py_None;
393         
394         values = PyTuple_New(ci->values_num); /* New reference. */
395         for (i = 0; i < ci->values_num; ++i) {
396                 if (ci->values[i].type == OCONFIG_TYPE_STRING) {
397                         PyTuple_SET_ITEM(values, i, PyString_FromString(ci->values[i].value.string));
398                 } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
399                         PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number));
400                 } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
401                         PyTuple_SET_ITEM(values, i, PyBool_FromLong(ci->values[i].value.boolean));
402                 }
403         }
404         
405         item = PyObject_CallFunction((PyObject *) &ConfigType, "sONO", ci->key, parent, values, Py_None);
406         if (item == NULL)
407                 return NULL;
408         children = PyTuple_New(ci->children_num); /* New reference. */
409         for (i = 0; i < ci->children_num; ++i) {
410                         PyTuple_SET_ITEM(children, i, cpy_oconfig_to_pyconfig(ci->children + i, item));
411         }
412         tmp = ((Config *) item)->children;
413         ((Config *) item)->children = children;
414         Py_XDECREF(tmp);
415         return item;
416 }
417
418 static int cpy_config(oconfig_item_t *ci) {
419         int i;
420         PyObject *sys;
421         PyObject *sys_path;
422         PyObject *module;
423         
424         /* Ok in theory we shouldn't do initialization at this point
425          * but we have to. In order to give python scripts a chance
426          * to register a config callback we need to be able to execute
427          * python code during the config callback so we have to start
428          * the interpreter here. */
429         /* Do *not* use the python "thread" module at this point! */
430         Py_Initialize();
431         
432         PyType_Ready(&ConfigType);
433         sys = PyImport_ImportModule("sys"); /* New reference. */
434         if (sys == NULL) {
435                 ERROR("python module: Unable to import \"sys\" module.");
436                 /* Just print the default python exception text to stderr. */
437                 PyErr_Print();
438                 return 1;
439         }
440         sys_path = PyObject_GetAttrString(sys, "path"); /* New reference. */
441         Py_DECREF(sys);
442         if (sys_path == NULL) {
443                 ERROR("python module: Unable to read \"sys.path\".");
444                 PyErr_Print();
445                 return 1;
446         }
447         module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
448         PyModule_AddObject(module, "Config", (PyObject *) &ConfigType); /* Steals a reference. */
449         PyModule_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
450         PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
451         PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
452         PyModule_AddIntConstant(module, "LOG_WARNING", LOG_WARNING);
453         PyModule_AddIntConstant(module, "LOG_ERROR", LOG_ERR);
454         for (i = 0; i < ci->children_num; ++i) {
455                 oconfig_item_t *item = ci->children + i;
456                 
457                 if (strcasecmp(item->key, "ModulePath") == 0) {
458                         char *dir = NULL;
459                         PyObject *dir_object;
460                         
461                         if (cf_util_get_string(item, &dir) != 0) 
462                                 continue;
463                         dir_object = PyString_FromString(dir); /* New reference. */
464                         if (dir_object == NULL) {
465                                 ERROR("python plugin: Unable to convert \"%s\" to "
466                                       "a python object.", dir);
467                                 free(dir);
468                                 PyErr_Print();
469                                 continue;
470                         }
471                         if (PyList_Append(sys_path, dir_object) != 0) {
472                                 ERROR("python plugin: Unable to append \"%s\" to "
473                                       "python module path.", dir);
474                                 PyErr_Print();
475                         }
476                         Py_DECREF(dir_object);
477                         free(dir);
478                 } else if (strcasecmp(item->key, "Import") == 0) {
479                         char *module_name = NULL;
480                         PyObject *module;
481                         
482                         if (cf_util_get_string(item, &module_name) != 0) 
483                                 continue;
484                         module = PyImport_ImportModule(module_name); /* New reference. */
485                         if (module == NULL) {
486                                 ERROR("python plugin: Error importing module \"%s\".", module_name);
487                                 PyErr_Print();
488                         }
489                         free(module_name);
490                         Py_XDECREF(module);
491                 } else if (strcasecmp(item->key, "Module") == 0) {
492                         char *name = NULL;
493                         cpy_callback_t *c;
494                         PyObject *ret;
495                         
496                         if (cf_util_get_string(item, &name) != 0)
497                                 continue;
498                         for (c = cpy_config_callbacks; c; c = c->next) {
499                                 if (strcasecmp(c->name, name) == 0)
500                                         break;
501                         }
502                         if (c == NULL) {
503                                 WARNING("python plugin: Found a configuration for the \"%s\" plugin, "
504                                         "but the plugin isn't loaded or didn't register "
505                                         "a configuration callback.", name);
506                                 free(name);
507                                 continue;
508                         }
509                         free(name);
510                         if (c->data == NULL)
511                                 ret = PyObject_CallFunction(c->callback, "N",
512                                         cpy_oconfig_to_pyconfig(item, NULL)); /* New reference. */
513                         else
514                                 ret = PyObject_CallFunction(c->callback, "NO",
515                                         cpy_oconfig_to_pyconfig(item, NULL), c->data); /* New reference. */
516                         if (ret == NULL)
517                                 PyErr_Print();
518                         else
519                                 Py_DECREF(ret);
520                 } else {
521                         WARNING("python plugin: Ignoring unknown config key \"%s\".", item->key);
522                 }
523         }
524         Py_DECREF(sys_path);
525         return 0;
526 }
527
528 void module_register(void) {
529         plugin_register_complex_config("python", cpy_config);
530         plugin_register_init("python", cpy_init);
531 //      plugin_register_read("python", cna_read);
532         plugin_register_shutdown("python", cpy_shutdown);
533 }