LCOV - code coverage report
Current view: top level - src/plugins/python - python.cpp (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 51 189 27.0 %
Date: 2019-09-12 12:28:41 Functions: 12 22 54.5 %

          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             : }

Generated by: LCOV version 1.13