Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Plugin which acts as proxy and calls other plugins written in python
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #ifndef SWIG_TYPE_TABLE
11 : #error Build system error, SWIG_TYPE_TABLE is not defined
12 : #endif
13 :
14 : #include <Python.h>
15 :
16 : #ifndef HAVE_KDBCONFIG
17 : #include <kdbconfig.h>
18 : #endif
19 : #include <kdbhelper.h>
20 : #include SWIG_RUNTIME
21 : #include "python.hpp"
22 :
23 : #include <config.h>
24 : #include <kdbpluginprocess.h>
25 : #include <key.hpp>
26 : #include <keyset.hpp>
27 : #include <libgen.h>
28 : #include <mutex>
29 :
30 : using namespace ckdb;
31 : #include <kdberrors.h>
32 :
33 : #define PYTHON_PLUGIN_NAME_STR2(x) ELEKTRA_QUOTE (x)
34 : #define PYTHON_PLUGIN_NAME_STR PYTHON_PLUGIN_NAME_STR2 (PYTHON_PLUGIN_NAME)
35 :
36 0 : static PyObject * Python_fromSWIG (ckdb::Key * key)
37 : {
38 0 : swig_type_info * ti = SWIG_TypeQuery ("kdb::Key *");
39 0 : if (key == nullptr || ti == nullptr) return Py_None;
40 0 : return SWIG_NewPointerObj (new kdb::Key (key), ti, 0);
41 : }
42 :
43 0 : static PyObject * Python_fromSWIG (ckdb::KeySet * keyset)
44 : {
45 0 : swig_type_info * ti = SWIG_TypeQuery ("kdb::KeySet *");
46 0 : if (keyset == nullptr || ti == nullptr) return Py_None;
47 0 : return SWIG_NewPointerObj (new kdb::KeySet (keyset), ti, 0);
48 : }
49 :
50 : class Python_LockSwap
51 : {
52 : public:
53 : Python_LockSwap (PyThreadState * newstate)
54 : {
55 0 : gstate = PyGILState_Ensure ();
56 0 : tstate = PyThreadState_Swap (newstate);
57 : }
58 :
59 : ~Python_LockSwap ()
60 : {
61 0 : PyThreadState_Swap (tstate);
62 0 : PyGILState_Release (gstate);
63 : }
64 :
65 : private:
66 : PyGILState_STATE gstate;
67 : PyThreadState * tstate;
68 : };
69 :
70 : typedef struct
71 : {
72 : PyThreadState * tstate;
73 : PyObject * instance;
74 : Key * script;
75 : int printError;
76 : } moduleData;
77 :
78 0 : static int Python_AppendToSysPath (const char * path)
79 : {
80 0 : if (path == nullptr) return 0;
81 :
82 0 : PyObject * sysPath = PySys_GetObject ((char *) "path");
83 0 : PyObject * pyPath = PyUnicode_FromString (path);
84 0 : PyList_Append (sysPath, pyPath);
85 0 : Py_DECREF (pyPath);
86 : return 1;
87 : }
88 :
89 0 : static PyObject * Python_CallFunction (PyObject * object, PyObject * args)
90 : {
91 0 : if (!PyCallable_Check (object)) return nullptr;
92 :
93 0 : PyObject * res = PyObject_CallObject (object, args ? args : PyTuple_New (0));
94 0 : Py_XINCREF (res);
95 : return res;
96 : }
97 :
98 0 : static int Python_CallFunction_Int (moduleData * data, PyObject * object, PyObject * args, ckdb::Key * errorKey)
99 : {
100 0 : int ret = -1;
101 0 : PyObject * res = Python_CallFunction (object, args);
102 0 : if (!res)
103 : {
104 0 : ELEKTRA_SET_PLUGIN_MISBEHAVIOR_ERRORF (errorKey, "Error while calling python function of script %s%s",
105 : keyString (data->script),
106 : data->printError ? "" : ", use /print to print error messages");
107 0 : if (data->printError) PyErr_Print ();
108 : }
109 : else
110 : {
111 : #if PY_MAJOR_VERSION >= 3
112 0 : if (!PyLong_Check (res))
113 : #else
114 0 : if (!PyInt_Check (res))
115 : #endif
116 0 : ELEKTRA_SET_PLUGIN_MISBEHAVIOR_ERROR (errorKey, "Python return value is no integer");
117 : else
118 0 : ret = PyLong_AsLong (res);
119 : }
120 :
121 0 : Py_XDECREF (res);
122 0 : return ret;
123 : }
124 :
125 0 : static int Python_CallFunction_Helper1 (moduleData * data, const char * funcName, ckdb::Key * errorKey)
126 : {
127 0 : int ret = 0;
128 0 : Python_LockSwap pylock (data->tstate);
129 0 : PyObject * func = PyObject_GetAttrString (data->instance, funcName);
130 0 : if (func)
131 : {
132 0 : PyObject * arg0 = Python_fromSWIG (errorKey);
133 0 : PyObject * args = Py_BuildValue ("(O)", arg0);
134 0 : ret = Python_CallFunction_Int (data, func, args, errorKey);
135 0 : Py_DECREF (arg0);
136 0 : Py_DECREF (args);
137 0 : Py_DECREF (func);
138 : }
139 0 : return ret;
140 : }
141 :
142 0 : static int Python_CallFunction_Helper2 (moduleData * data, const char * funcName, ckdb::KeySet * returned, ckdb::Key * parentKey)
143 : {
144 0 : int ret = 0;
145 0 : Python_LockSwap pylock (data->tstate);
146 0 : PyObject * func = PyObject_GetAttrString (data->instance, funcName);
147 0 : if (func)
148 : {
149 0 : PyObject * arg0 = Python_fromSWIG (returned);
150 0 : PyObject * arg1 = Python_fromSWIG (parentKey);
151 0 : PyObject * args = Py_BuildValue ("(OO)", arg0, arg1);
152 0 : ret = Python_CallFunction_Int (data, func, args, parentKey);
153 0 : Py_DECREF (arg0);
154 0 : Py_DECREF (arg1);
155 0 : Py_DECREF (args);
156 0 : Py_DECREF (func);
157 : }
158 0 : return ret;
159 : }
160 :
161 0 : static void Python_Shutdown (moduleData * data)
162 : {
163 : /* destroy python if plugin isn't used anymore */
164 0 : if (Py_IsInitialized ())
165 : {
166 : // Do we have a sub interpreter?
167 0 : if (data->tstate)
168 : {
169 0 : Python_LockSwap pylock (data->tstate);
170 :
171 : /* clean up references */
172 0 : Py_XDECREF (data->instance);
173 0 : data->instance = nullptr;
174 :
175 : /* destroy sub interpreter */
176 0 : Py_EndInterpreter (data->tstate);
177 : }
178 0 : Py_Finalize ();
179 : }
180 0 : }
181 :
182 50 : static moduleData * createModuleData (ckdb::Plugin * handle)
183 : {
184 50 : KeySet * config = elektraPluginGetConfig (handle);
185 :
186 50 : Key * script = ksLookupByName (config, "/script", 0);
187 50 : if (script == nullptr || !strlen (keyString (script)))
188 : {
189 : return 0;
190 : }
191 :
192 : /* create module data */
193 10 : auto data = new moduleData;
194 10 : data->tstate = nullptr;
195 10 : data->instance = nullptr;
196 10 : data->script = script;
197 10 : data->printError = (ksLookupByName (config, "/print", 0) != nullptr);
198 10 : return data;
199 : }
200 :
201 : extern "C" {
202 50 : int PYTHON_PLUGIN_FUNCTION (Open) (ckdb::Plugin * handle, ckdb::Key * errorKey)
203 : {
204 50 : ElektraPluginProcess * pp = static_cast<ElektraPluginProcess *> (elektraPluginGetData (handle));
205 50 : if (pp == nullptr)
206 : {
207 50 : moduleData * md = createModuleData (handle);
208 50 : if (!md)
209 : {
210 40 : if (ksLookupByName (elektraPluginGetConfig (handle), "/module", 0) != nullptr)
211 : {
212 : return ELEKTRA_PLUGIN_STATUS_SUCCESS; // by convention: success if /module exists
213 : }
214 :
215 : // TODO: Solution
216 4 : ELEKTRA_SET_INTERFACE_ERROR (errorKey, "No python script set, please pass a filename via /script");
217 4 : return ELEKTRA_PLUGIN_STATUS_ERROR;
218 : }
219 :
220 10 : if ((pp = elektraPluginProcessInit (errorKey)) == nullptr) return ELEKTRA_PLUGIN_STATUS_ERROR;
221 :
222 10 : elektraPluginProcessSetData (pp, md);
223 10 : elektraPluginSetData (handle, pp);
224 10 : if (!elektraPluginProcessIsParent (pp)) elektraPluginProcessStart (handle, pp);
225 : }
226 :
227 10 : if (elektraPluginProcessIsParent (pp)) return elektraPluginProcessOpen (pp, errorKey);
228 :
229 0 : moduleData * data = static_cast<moduleData *> (elektraPluginProcessGetData (pp));
230 :
231 0 : if (data->instance == nullptr)
232 : {
233 : /* initialize python interpreter if necessary */
234 0 : if (!Py_IsInitialized ())
235 : {
236 0 : Py_Initialize ();
237 0 : if (!Py_IsInitialized ())
238 : {
239 : goto error;
240 : }
241 : }
242 : /* init threads */
243 0 : PyEval_InitThreads ();
244 :
245 : /* acquire GIL */
246 0 : Python_LockSwap pylock (nullptr);
247 :
248 : /* create a new sub interpreter */
249 0 : data->tstate = Py_NewInterpreter ();
250 0 : if (data->tstate == nullptr)
251 : {
252 0 : ELEKTRA_SET_INSTALLATION_ERROR (errorKey, "Unable to create sub interpreter");
253 : goto error;
254 : }
255 0 : PyThreadState_Swap (data->tstate);
256 :
257 : /* extend sys path for kdb module */
258 0 : if (!Python_AppendToSysPath (ELEKTRA_PYTHON_SITE_PACKAGES))
259 : {
260 0 : ELEKTRA_SET_INSTALLATION_ERRORF (errorKey, "Unable to extend sys.path with built-in path '%s'",
261 : ELEKTRA_PYTHON_SITE_PACKAGES);
262 : goto error;
263 : }
264 :
265 : /* extend sys path with user-defined path */
266 0 : const char * mname = keyString (ksLookupByName (elektraPluginGetConfig (handle), "/python/path", 0));
267 0 : if (!Python_AppendToSysPath (mname))
268 : {
269 0 : ELEKTRA_SET_INSTALLATION_ERRORF (errorKey, "Unable to extend sys.path with user-defined /python/path '%s'", mname);
270 : goto error;
271 : }
272 :
273 : /* import kdb */
274 0 : PyObject * kdbModule = PyImport_ImportModule ("kdb");
275 0 : if (kdbModule == nullptr)
276 : {
277 0 : ELEKTRA_SET_INSTALLATION_ERROR (errorKey, "Unable to import kdb module");
278 0 : goto error_print;
279 : }
280 0 : Py_XDECREF (kdbModule);
281 :
282 : /* extend sys path for standard plugins */
283 0 : if (!Python_AppendToSysPath (ELEKTRA_PYTHON_PLUGIN_FOLDER))
284 : {
285 0 : ELEKTRA_SET_INSTALLATION_ERRORF (errorKey, "Unable to extend sys.path with built-in plugin path '%s'",
286 : ELEKTRA_PYTHON_PLUGIN_FOLDER);
287 : goto error;
288 : }
289 :
290 : /* extend sys path */
291 0 : char * tmpScript = elektraStrDup (keyString (data->script));
292 0 : const char * dname = dirname (tmpScript);
293 0 : if (!Python_AppendToSysPath (dname))
294 : {
295 0 : ELEKTRA_SET_INSTALLATION_ERRORF (errorKey, "Unable to extend sys.path with script dirname '%s'", dname);
296 0 : elektraFree (tmpScript);
297 : goto error;
298 : }
299 0 : elektraFree (tmpScript);
300 :
301 : /* import module/script */
302 0 : tmpScript = elektraStrDup (keyString (data->script));
303 0 : char * bname = basename (tmpScript);
304 0 : size_t bname_len = strlen (bname);
305 0 : if (bname_len >= 4 && strcmp (bname + bname_len - 3, ".py") == 0) bname[bname_len - 3] = '\0';
306 :
307 0 : PyObject * pModule = PyImport_ImportModule (bname);
308 0 : if (pModule == nullptr)
309 : {
310 0 : ELEKTRA_SET_INSTALLATION_ERRORF (errorKey, "Unable to import python script '%s'", keyString (data->script));
311 0 : elektraFree (tmpScript);
312 : goto error_print;
313 : }
314 0 : elektraFree (tmpScript);
315 :
316 : /* get class */
317 0 : PyObject * klass = PyObject_GetAttrString (pModule, "ElektraPlugin");
318 0 : Py_DECREF (pModule);
319 0 : if (klass == nullptr)
320 : {
321 0 : ELEKTRA_SET_INTERFACE_ERROR (errorKey, "Module doesn't provide a ElektraPlugin class");
322 : goto error_print;
323 : }
324 :
325 : /* create instance of class */
326 0 : PyObject * inst_args = Py_BuildValue ("()");
327 0 : PyObject * inst = PyEval_CallObject (klass, inst_args);
328 0 : Py_DECREF (klass);
329 0 : Py_DECREF (inst_args);
330 0 : if (inst == nullptr)
331 : {
332 0 : ELEKTRA_SET_PLUGIN_MISBEHAVIOR_ERROR (errorKey, "Unable to create instance of ElektraPlugin");
333 : goto error_print;
334 : }
335 0 : data->instance = inst;
336 : }
337 :
338 : /* call python function */
339 0 : return Python_CallFunction_Helper2 (data, "open", elektraPluginGetConfig (handle), errorKey);
340 :
341 : error_print:
342 0 : if (data->printError) PyErr_Print ();
343 : error:
344 : /* destroy python */
345 0 : Python_Shutdown (data);
346 0 : delete data;
347 0 : elektraPluginProcessSetData (pp, nullptr);
348 0 : return ELEKTRA_PLUGIN_STATUS_ERROR;
349 : }
350 :
351 50 : int PYTHON_PLUGIN_FUNCTION (Close) (ckdb::Plugin * handle, ckdb::Key * errorKey)
352 : {
353 50 : ElektraPluginProcess * pp = static_cast<ElektraPluginProcess *> (elektraPluginGetData (handle));
354 50 : if (!pp) return ELEKTRA_PLUGIN_STATUS_SUCCESS;
355 10 : moduleData * data = static_cast<moduleData *> (elektraPluginProcessGetData (pp));
356 10 : if (elektraPluginProcessIsParent (pp))
357 : {
358 10 : ElektraPluginProcessCloseResult result = elektraPluginProcessClose (pp, errorKey);
359 10 : if (result.cleanedUp)
360 : {
361 10 : delete data;
362 10 : elektraPluginSetData (handle, NULL);
363 : }
364 10 : return result.result;
365 : }
366 :
367 0 : if (data == nullptr) return ELEKTRA_PLUGIN_STATUS_SUCCESS;
368 :
369 0 : int ret = Python_CallFunction_Helper1 (data, "close", errorKey);
370 : /* destroy python */
371 0 : Python_Shutdown (data);
372 0 : delete data;
373 0 : return ret;
374 : }
375 :
376 40 : int PYTHON_PLUGIN_FUNCTION (Get) (ckdb::Plugin * handle, ckdb::KeySet * returned, ckdb::Key * parentKey)
377 : {
378 : #define _MODULE_CONFIG_PATH "system/elektra/modules/" PYTHON_PLUGIN_NAME_STR
379 40 : if (!strcmp (keyName (parentKey), _MODULE_CONFIG_PATH))
380 : {
381 : KeySet * n;
382 : ksAppend (returned,
383 36 : n = ksNew (30, keyNew (_MODULE_CONFIG_PATH, KEY_VALUE, "python interpreter waits for your orders", KEY_END),
384 : keyNew (_MODULE_CONFIG_PATH "/exports", KEY_END),
385 : keyNew (_MODULE_CONFIG_PATH "/exports/get", KEY_FUNC, PYTHON_PLUGIN_FUNCTION (Get), KEY_END),
386 : keyNew (_MODULE_CONFIG_PATH "/exports/set", KEY_FUNC, PYTHON_PLUGIN_FUNCTION (Set), KEY_END),
387 : keyNew (_MODULE_CONFIG_PATH "/exports/error", KEY_FUNC, PYTHON_PLUGIN_FUNCTION (Error), KEY_END),
388 : keyNew (_MODULE_CONFIG_PATH "/exports/open", KEY_FUNC, PYTHON_PLUGIN_FUNCTION (Open), KEY_END),
389 : keyNew (_MODULE_CONFIG_PATH "/exports/close", KEY_FUNC, PYTHON_PLUGIN_FUNCTION (Close), KEY_END),
390 : #include ELEKTRA_README
391 36 : keyNew (_MODULE_CONFIG_PATH "/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END));
392 36 : ksDel (n);
393 : }
394 :
395 40 : ElektraPluginProcess * pp = static_cast<ElektraPluginProcess *> (elektraPluginGetData (handle));
396 40 : if (!pp) return 0;
397 4 : if (elektraPluginProcessIsParent (pp)) return elektraPluginProcessSend (pp, ELEKTRA_PLUGINPROCESS_GET, returned, parentKey);
398 :
399 0 : moduleData * data = static_cast<moduleData *> (elektraPluginProcessGetData (pp));
400 0 : if (data == nullptr) return 0;
401 0 : return Python_CallFunction_Helper2 (data, "get", returned, parentKey);
402 : }
403 :
404 2 : int PYTHON_PLUGIN_FUNCTION (Set) (ckdb::Plugin * handle, ckdb::KeySet * returned, ckdb::Key * parentKey)
405 : {
406 2 : ElektraPluginProcess * pp = static_cast<ElektraPluginProcess *> (elektraPluginGetData (handle));
407 2 : if (!pp) return 0;
408 2 : if (elektraPluginProcessIsParent (pp)) return elektraPluginProcessSend (pp, ELEKTRA_PLUGINPROCESS_SET, returned, parentKey);
409 :
410 0 : moduleData * data = static_cast<moduleData *> (elektraPluginProcessGetData (pp));
411 0 : if (data == nullptr) return 0;
412 0 : return Python_CallFunction_Helper2 (data, "set", returned, parentKey);
413 : }
414 :
415 2 : int PYTHON_PLUGIN_FUNCTION (Error) (ckdb::Plugin * handle, ckdb::KeySet * returned, ckdb::Key * parentKey)
416 : {
417 2 : ElektraPluginProcess * pp = static_cast<ElektraPluginProcess *> (elektraPluginGetData (handle));
418 2 : if (!pp) return 0;
419 2 : if (elektraPluginProcessIsParent (pp)) return elektraPluginProcessSend (pp, ELEKTRA_PLUGINPROCESS_ERROR, returned, parentKey);
420 :
421 0 : moduleData * data = static_cast<moduleData *> (elektraPluginProcessGetData (pp));
422 0 : if (data == nullptr) return 0;
423 0 : return Python_CallFunction_Helper2 (data, "error", returned, parentKey);
424 : }
425 :
426 50 : ckdb::Plugin * PYTHON_PLUGIN_EXPORT (PYTHON_PLUGIN_NAME)
427 : {
428 : // clang-format off
429 : return elektraPluginExport(PYTHON_PLUGIN_NAME_STR,
430 : ELEKTRA_PLUGIN_OPEN, &PYTHON_PLUGIN_FUNCTION(Open),
431 : ELEKTRA_PLUGIN_CLOSE, &PYTHON_PLUGIN_FUNCTION(Close),
432 : ELEKTRA_PLUGIN_GET, &PYTHON_PLUGIN_FUNCTION(Get),
433 : ELEKTRA_PLUGIN_SET, &PYTHON_PLUGIN_FUNCTION(Set),
434 : ELEKTRA_PLUGIN_ERROR, &PYTHON_PLUGIN_FUNCTION(Error),
435 50 : ELEKTRA_PLUGIN_END);
436 : }
437 : }
|