Mercurial > hg > orthanc-python
diff Sources/PythonLock.cpp @ 0:7ed502b17b8f
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 26 Mar 2020 18:47:01 +0100 |
parents | |
children | 0b8239ce1bec |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sources/PythonLock.cpp Thu Mar 26 18:47:01 2020 +0100 @@ -0,0 +1,470 @@ +/** + * Python plugin for Orthanc + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PythonLock.h" + +#include "PythonFunction.h" +#include "PythonModule.h" + +#include <OrthancPluginCppWrapper.h> + +#include <boost/thread/mutex.hpp> + +static boost::mutex mutex_; +static PyThreadState* interpreterState_ = NULL; +static PythonLock::ModuleFunctionsInstaller moduleFunctions_ = NULL; +static PythonLock::ModuleClassesInstaller moduleClasses_ = NULL; +static std::string moduleName_; +static std::string exceptionName_; + + +struct module_state +{ + PyObject *exceptionClass_ = NULL; +}; + +#if PY_MAJOR_VERSION >= 3 +# define GETSTATE(module) ((struct module_state*) PyModule_GetState(module)) +#else +# define GETSTATE(module) (&_state) +static struct module_state _state; +#endif + + +PythonLock::PythonLock() +{ + //OrthancPlugins::LogInfo("Python lock (GIL) acquired"); + gstate_ = PyGILState_Ensure(); +} + + +PythonLock::~PythonLock() +{ + PyGILState_Release(gstate_); + //OrthancPlugins::LogInfo("Python lock (GIL) released"); +} + + +void PythonLock::ExecuteCommand(const std::string& s) +{ + if (PyRun_SimpleString(s.c_str()) != 0) + { + OrthancPlugins::LogError("Error while executing a Python command"); + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } +} + + +void PythonLock::ExecuteFile(const std::string& path) +{ + OrthancPlugins::MemoryBuffer buffer; + buffer.ReadFile(path); + + std::string script; + buffer.ToString(script); + + ExecuteCommand(script); +} + + +bool PythonLock::HasErrorOccurred(std::string& target) +{ + if (PyErr_Occurred()) + { + PyObject *exceptionType = NULL; + PyObject *exceptionValue = NULL; + PyObject *traceback = NULL; + PyErr_Fetch(&exceptionType, &exceptionValue, &traceback); + + if (exceptionType == NULL) + { + return false; + } + + PyErr_NormalizeException(&exceptionType, &exceptionValue, &traceback); + +#if PY_MAJOR_VERSION >= 3 + if (traceback != NULL) + { + PyException_SetTraceback(exceptionValue, traceback); + } +#endif + + if (exceptionType != NULL) + { + PythonObject temp(*this, PyObject_Str(exceptionType)); + std::string s; + if (temp.ToUtf8String(s)) + { + target += s + "\n"; + } + } + + if (exceptionValue != NULL) + { + PythonObject temp(*this, PyObject_Str(exceptionValue)); + std::string s; + if (temp.ToUtf8String(s)) + { + target += s + "\n"; + } + } + + { + PythonModule module(*this, "traceback"); + PythonFunction f(*this, module, "format_tb"); + + if (traceback != NULL && + f.IsValid()) + { + PythonObject args(*this, PyTuple_New(1)); + PyTuple_SetItem(args.GetPyObject(), 0, traceback); + + std::auto_ptr<PythonObject> value(f.CallUnchecked(args.GetPyObject())); + + if (value->IsValid()) + { + Py_ssize_t len = PyList_Size(value->GetPyObject()); + for (Py_ssize_t i = 0; i < len; i++) + { + PythonObject item(*this, PyList_GetItem(value->GetPyObject(), i), true /* borrowed */); + std::string line; + if (item.ToUtf8String(line)) + { + target += "\n" + line; + } + } + } + } + } + + + /** + * "This call takes away a reference to each object: you must own + * a reference to each object before the call and after the call + * you no longer own these references. (If you don't understand + * this, don't use this function. I warned you.)" + * => I don't use PyErr_Restore() + **/ + + //PyErr_Restore(exceptionType, exceptionValue, traceback); + //PyErr_Clear(); + + return true; + } + else + { + return false; + } +} + + +static void RegisterException(PyObject* module, + const std::string& name) +{ + struct module_state *state = GETSTATE(module); + + state->exceptionClass_ = PyErr_NewException(const_cast<char*>(name.c_str()), NULL, NULL); + if (state->exceptionClass_ == NULL) + { + Py_DECREF(module); + OrthancPlugins::LogError("Cannot create the Python exception class"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + +#if 1 + Py_XINCREF(state->exceptionClass_); + if (PyModule_AddObject(module, "Exception", state->exceptionClass_) < 0) + { + Py_XDECREF(state->exceptionClass_); + Py_CLEAR(state->exceptionClass_); + OrthancPlugins::LogError("Cannot create the Python exception class"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } +#else + // Install the exception class + PyObject* dict = PyModule_GetDict(module); + PyDict_SetItemString(dict, "Exception", state->exceptionClass); +#endif +} + + + +#if PY_MAJOR_VERSION >= 3 + +static int sdk_traverse(PyObject *module, visitproc visit, void *arg) +{ + Py_VISIT(GETSTATE(module)->exceptionClass_); + return 0; +} + +static int sdk_clear(PyObject *module) +{ + Py_CLEAR(GETSTATE(module)->exceptionClass_); + return 0; +} + +static struct PyModuleDef moduledef = +{ + PyModuleDef_HEAD_INIT, + NULL, /* m_name => TO BE FILLED */ + NULL, + sizeof(struct module_state), + NULL, /* m_methods => TO BE FILLED */ + NULL, + sdk_traverse, + sdk_clear, + NULL +}; + + +PyMODINIT_FUNC InitializeModule() +{ + if (moduleFunctions_ == NULL || + moduleClasses_ == NULL || + moduleName_.empty() || + exceptionName_.empty()) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + moduledef.m_name = moduleName_.c_str(); + moduledef.m_methods = moduleFunctions_(); + + PyObject *module = PyModule_Create(&moduledef); + if (module == NULL) + { + OrthancPlugins::LogError("Cannot create a Python module"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + RegisterException(module, moduleName_ + "." + exceptionName_); + moduleClasses_(module); + + return module; +} + +#else + +void InitializeModule() +{ + if (moduleFunctions_ == NULL || + moduleClasses_ == NULL || + moduleName_.empty() || + exceptionName_.empty()) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + PyObject *module = Py_InitModule(moduleName_.c_str(), moduleFunctions_()); + if (module == NULL) + { + OrthancPlugins::LogError("Cannot create a Python module"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + RegisterException(module, moduleName_ + "." + exceptionName_); + moduleClasses_(module); +} + +#endif + + + +void PythonLock::GlobalInitialize(const std::string& moduleName, + const std::string& exceptionName, + ModuleFunctionsInstaller moduleFunctions, + ModuleClassesInstaller moduleClasses, + bool verbose) +{ + boost::mutex::scoped_lock lock(mutex_); + + if (interpreterState_ != NULL) + { + OrthancPlugins::LogError("Cannot initialize twice the Python interpreter"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + + if (moduleClasses == NULL || + moduleFunctions == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); + } + + if (moduleName.empty() || + exceptionName.empty()) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + + if (exceptionName.find('.') != std::string::npos) + { + OrthancPlugins::LogError("The name of the exception cannot contain \".\", found: " + + exceptionName); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + + moduleClasses_ = moduleClasses; + moduleFunctions_ = moduleFunctions; + moduleName_ = moduleName; + exceptionName_ = exceptionName; + + std::string executable; + + { + OrthancPlugins::OrthancString str; + str.Assign(OrthancPluginGetOrthancPath(OrthancPlugins::GetGlobalContext())); + str.ToString(executable); + } + + OrthancPlugins::LogWarning("Program name: " + executable); + +#if PY_MAJOR_VERSION == 2 + Py_SetProgramName(&executable[0]); /* optional but recommended */ +#else + std::wstring wide(executable.begin(), executable.end()); + Py_SetProgramName(&wide[0]); /* optional but recommended */ + Py_UnbufferedStdioFlag = 1; // Write immediately to stdout after "sys.stdout.flush()" (only in Python 3.x) +#endif + + Py_InspectFlag = 1; // Don't exit the Orthanc process on Python error + + if (verbose) + { + Py_VerboseFlag = 1; + } + +#if PY_MAJOR_VERSION >= 3 + PyImport_AppendInittab(moduleName_.c_str(), InitializeModule); +#endif + +#if PY_VERSION_HEX < 0x03020000 /* 3.2.0 */ + /** + * "Changed in version 3.2: This function cannot be called before + * Py_Initialize() anymore." + **/ + if (!PyEval_ThreadsInitialized()) + { + PyEval_InitThreads(); + } +#endif + + Py_InitializeEx(0 /* Python is embedded, skip signal handlers */); + +#if PY_MAJOR_VERSION == 2 + std::cout << Py_GetPath() << std::endl; + std::cout << Py_GetProgramName() << std::endl; + std::cout << Py_GetProgramFullPath() << std::endl; +#else + std::wcout << Py_GetPath() << std::endl; + std::wcout << Py_GetProgramName() << std::endl; + std::wcout << Py_GetProgramFullPath() << std::endl; +#endif + + +#if (PY_VERSION_HEX >= 0x03020000 /* 3.2.0 */ && \ + PY_VERSION_HEX < 0x03070000 /* 3.7.0 */) + /** + * Changed in version 3.7: This function is now called by + * Py_Initialize(), so you don't have to call it yourself anymore. + **/ + if (!PyEval_ThreadsInitialized()) + { + PyEval_InitThreads(); + } +#endif + + +#if PY_MAJOR_VERSION == 2 + InitializeModule(); +#endif + + // https://fr.slideshare.net/YiLungTsai/embed-python => slide 26 + interpreterState_ = PyEval_SaveThread(); +} + + +void PythonLock::AddSysPath(const std::string& path) +{ + /** + * "It is recommended that applications embedding the Python + * interpreter for purposes other than executing a single script + * pass 0 as updatepath, and update sys.path themselves if + * desired. See CVE-2008-5983." + * => Set "sys.path.append()" to the directory of the configuration file by default + **/ + + PythonLock lock; + + /** + * Initialization of "sys.path.append()" must be done before loading + * any module. + **/ + + PyObject *sysPath = PySys_GetObject(const_cast<char*>("path")); + if (sysPath == NULL) + { + OrthancPlugins::LogError("Cannot find sys.path"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + PyObject* pyPath = PyUnicode_FromString(path.c_str()); + int result = PyList_Insert(sysPath, 0, pyPath); + Py_DECREF(pyPath); + + if (result != 0) + { + OrthancPlugins::LogError("Cannot run sys.path.append()"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } +} + + +void PythonLock::GlobalFinalize() +{ + boost::mutex::scoped_lock lock(mutex_); + + if (interpreterState_ != NULL) + { + PyEval_RestoreThread(interpreterState_); + interpreterState_ = NULL; + } + + Py_Finalize(); +} + + +void PythonLock::RaiseException(PyObject* module, + OrthancPluginErrorCode code) +{ + if (code != OrthancPluginErrorCode_Success) + { + const char* message = OrthancPluginGetErrorDescription(OrthancPlugins::GetGlobalContext(), code); + + struct module_state *state = GETSTATE(module); + if (state->exceptionClass_ == NULL) + { + OrthancPlugins::LogError("No Python exception has been registered"); + } + else + { + PyErr_SetString(state->exceptionClass_, message); + } + } +}