comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:7ed502b17b8f
1 /**
2 * Python plugin for Orthanc
3 * Copyright (C) 2017-2020 Osimis S.A., Belgium
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the GNU Affero General Public License
7 * as published by the Free Software Foundation, either version 3 of
8 * the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 **/
18
19
20 #include "PythonLock.h"
21
22 #include "PythonFunction.h"
23 #include "PythonModule.h"
24
25 #include <OrthancPluginCppWrapper.h>
26
27 #include <boost/thread/mutex.hpp>
28
29 static boost::mutex mutex_;
30 static PyThreadState* interpreterState_ = NULL;
31 static PythonLock::ModuleFunctionsInstaller moduleFunctions_ = NULL;
32 static PythonLock::ModuleClassesInstaller moduleClasses_ = NULL;
33 static std::string moduleName_;
34 static std::string exceptionName_;
35
36
37 struct module_state
38 {
39 PyObject *exceptionClass_ = NULL;
40 };
41
42 #if PY_MAJOR_VERSION >= 3
43 # define GETSTATE(module) ((struct module_state*) PyModule_GetState(module))
44 #else
45 # define GETSTATE(module) (&_state)
46 static struct module_state _state;
47 #endif
48
49
50 PythonLock::PythonLock()
51 {
52 //OrthancPlugins::LogInfo("Python lock (GIL) acquired");
53 gstate_ = PyGILState_Ensure();
54 }
55
56
57 PythonLock::~PythonLock()
58 {
59 PyGILState_Release(gstate_);
60 //OrthancPlugins::LogInfo("Python lock (GIL) released");
61 }
62
63
64 void PythonLock::ExecuteCommand(const std::string& s)
65 {
66 if (PyRun_SimpleString(s.c_str()) != 0)
67 {
68 OrthancPlugins::LogError("Error while executing a Python command");
69 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
70 }
71 }
72
73
74 void PythonLock::ExecuteFile(const std::string& path)
75 {
76 OrthancPlugins::MemoryBuffer buffer;
77 buffer.ReadFile(path);
78
79 std::string script;
80 buffer.ToString(script);
81
82 ExecuteCommand(script);
83 }
84
85
86 bool PythonLock::HasErrorOccurred(std::string& target)
87 {
88 if (PyErr_Occurred())
89 {
90 PyObject *exceptionType = NULL;
91 PyObject *exceptionValue = NULL;
92 PyObject *traceback = NULL;
93 PyErr_Fetch(&exceptionType, &exceptionValue, &traceback);
94
95 if (exceptionType == NULL)
96 {
97 return false;
98 }
99
100 PyErr_NormalizeException(&exceptionType, &exceptionValue, &traceback);
101
102 #if PY_MAJOR_VERSION >= 3
103 if (traceback != NULL)
104 {
105 PyException_SetTraceback(exceptionValue, traceback);
106 }
107 #endif
108
109 if (exceptionType != NULL)
110 {
111 PythonObject temp(*this, PyObject_Str(exceptionType));
112 std::string s;
113 if (temp.ToUtf8String(s))
114 {
115 target += s + "\n";
116 }
117 }
118
119 if (exceptionValue != NULL)
120 {
121 PythonObject temp(*this, PyObject_Str(exceptionValue));
122 std::string s;
123 if (temp.ToUtf8String(s))
124 {
125 target += s + "\n";
126 }
127 }
128
129 {
130 PythonModule module(*this, "traceback");
131 PythonFunction f(*this, module, "format_tb");
132
133 if (traceback != NULL &&
134 f.IsValid())
135 {
136 PythonObject args(*this, PyTuple_New(1));
137 PyTuple_SetItem(args.GetPyObject(), 0, traceback);
138
139 std::auto_ptr<PythonObject> value(f.CallUnchecked(args.GetPyObject()));
140
141 if (value->IsValid())
142 {
143 Py_ssize_t len = PyList_Size(value->GetPyObject());
144 for (Py_ssize_t i = 0; i < len; i++)
145 {
146 PythonObject item(*this, PyList_GetItem(value->GetPyObject(), i), true /* borrowed */);
147 std::string line;
148 if (item.ToUtf8String(line))
149 {
150 target += "\n" + line;
151 }
152 }
153 }
154 }
155 }
156
157
158 /**
159 * "This call takes away a reference to each object: you must own
160 * a reference to each object before the call and after the call
161 * you no longer own these references. (If you don't understand
162 * this, don't use this function. I warned you.)"
163 * => I don't use PyErr_Restore()
164 **/
165
166 //PyErr_Restore(exceptionType, exceptionValue, traceback);
167 //PyErr_Clear();
168
169 return true;
170 }
171 else
172 {
173 return false;
174 }
175 }
176
177
178 static void RegisterException(PyObject* module,
179 const std::string& name)
180 {
181 struct module_state *state = GETSTATE(module);
182
183 state->exceptionClass_ = PyErr_NewException(const_cast<char*>(name.c_str()), NULL, NULL);
184 if (state->exceptionClass_ == NULL)
185 {
186 Py_DECREF(module);
187 OrthancPlugins::LogError("Cannot create the Python exception class");
188 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
189 }
190
191 #if 1
192 Py_XINCREF(state->exceptionClass_);
193 if (PyModule_AddObject(module, "Exception", state->exceptionClass_) < 0)
194 {
195 Py_XDECREF(state->exceptionClass_);
196 Py_CLEAR(state->exceptionClass_);
197 OrthancPlugins::LogError("Cannot create the Python exception class");
198 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
199 }
200 #else
201 // Install the exception class
202 PyObject* dict = PyModule_GetDict(module);
203 PyDict_SetItemString(dict, "Exception", state->exceptionClass);
204 #endif
205 }
206
207
208
209 #if PY_MAJOR_VERSION >= 3
210
211 static int sdk_traverse(PyObject *module, visitproc visit, void *arg)
212 {
213 Py_VISIT(GETSTATE(module)->exceptionClass_);
214 return 0;
215 }
216
217 static int sdk_clear(PyObject *module)
218 {
219 Py_CLEAR(GETSTATE(module)->exceptionClass_);
220 return 0;
221 }
222
223 static struct PyModuleDef moduledef =
224 {
225 PyModuleDef_HEAD_INIT,
226 NULL, /* m_name => TO BE FILLED */
227 NULL,
228 sizeof(struct module_state),
229 NULL, /* m_methods => TO BE FILLED */
230 NULL,
231 sdk_traverse,
232 sdk_clear,
233 NULL
234 };
235
236
237 PyMODINIT_FUNC InitializeModule()
238 {
239 if (moduleFunctions_ == NULL ||
240 moduleClasses_ == NULL ||
241 moduleName_.empty() ||
242 exceptionName_.empty())
243 {
244 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
245 }
246
247 moduledef.m_name = moduleName_.c_str();
248 moduledef.m_methods = moduleFunctions_();
249
250 PyObject *module = PyModule_Create(&moduledef);
251 if (module == NULL)
252 {
253 OrthancPlugins::LogError("Cannot create a Python module");
254 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
255 }
256
257 RegisterException(module, moduleName_ + "." + exceptionName_);
258 moduleClasses_(module);
259
260 return module;
261 }
262
263 #else
264
265 void InitializeModule()
266 {
267 if (moduleFunctions_ == NULL ||
268 moduleClasses_ == NULL ||
269 moduleName_.empty() ||
270 exceptionName_.empty())
271 {
272 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
273 }
274
275 PyObject *module = Py_InitModule(moduleName_.c_str(), moduleFunctions_());
276 if (module == NULL)
277 {
278 OrthancPlugins::LogError("Cannot create a Python module");
279 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
280 }
281
282 RegisterException(module, moduleName_ + "." + exceptionName_);
283 moduleClasses_(module);
284 }
285
286 #endif
287
288
289
290 void PythonLock::GlobalInitialize(const std::string& moduleName,
291 const std::string& exceptionName,
292 ModuleFunctionsInstaller moduleFunctions,
293 ModuleClassesInstaller moduleClasses,
294 bool verbose)
295 {
296 boost::mutex::scoped_lock lock(mutex_);
297
298 if (interpreterState_ != NULL)
299 {
300 OrthancPlugins::LogError("Cannot initialize twice the Python interpreter");
301 ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls);
302 }
303
304 if (moduleClasses == NULL ||
305 moduleFunctions == NULL)
306 {
307 ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer);
308 }
309
310 if (moduleName.empty() ||
311 exceptionName.empty())
312 {
313 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
314 }
315
316 if (exceptionName.find('.') != std::string::npos)
317 {
318 OrthancPlugins::LogError("The name of the exception cannot contain \".\", found: " +
319 exceptionName);
320 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
321 }
322
323 moduleClasses_ = moduleClasses;
324 moduleFunctions_ = moduleFunctions;
325 moduleName_ = moduleName;
326 exceptionName_ = exceptionName;
327
328 std::string executable;
329
330 {
331 OrthancPlugins::OrthancString str;
332 str.Assign(OrthancPluginGetOrthancPath(OrthancPlugins::GetGlobalContext()));
333 str.ToString(executable);
334 }
335
336 OrthancPlugins::LogWarning("Program name: " + executable);
337
338 #if PY_MAJOR_VERSION == 2
339 Py_SetProgramName(&executable[0]); /* optional but recommended */
340 #else
341 std::wstring wide(executable.begin(), executable.end());
342 Py_SetProgramName(&wide[0]); /* optional but recommended */
343 Py_UnbufferedStdioFlag = 1; // Write immediately to stdout after "sys.stdout.flush()" (only in Python 3.x)
344 #endif
345
346 Py_InspectFlag = 1; // Don't exit the Orthanc process on Python error
347
348 if (verbose)
349 {
350 Py_VerboseFlag = 1;
351 }
352
353 #if PY_MAJOR_VERSION >= 3
354 PyImport_AppendInittab(moduleName_.c_str(), InitializeModule);
355 #endif
356
357 #if PY_VERSION_HEX < 0x03020000 /* 3.2.0 */
358 /**
359 * "Changed in version 3.2: This function cannot be called before
360 * Py_Initialize() anymore."
361 **/
362 if (!PyEval_ThreadsInitialized())
363 {
364 PyEval_InitThreads();
365 }
366 #endif
367
368 Py_InitializeEx(0 /* Python is embedded, skip signal handlers */);
369
370 #if PY_MAJOR_VERSION == 2
371 std::cout << Py_GetPath() << std::endl;
372 std::cout << Py_GetProgramName() << std::endl;
373 std::cout << Py_GetProgramFullPath() << std::endl;
374 #else
375 std::wcout << Py_GetPath() << std::endl;
376 std::wcout << Py_GetProgramName() << std::endl;
377 std::wcout << Py_GetProgramFullPath() << std::endl;
378 #endif
379
380
381 #if (PY_VERSION_HEX >= 0x03020000 /* 3.2.0 */ && \
382 PY_VERSION_HEX < 0x03070000 /* 3.7.0 */)
383 /**
384 * Changed in version 3.7: This function is now called by
385 * Py_Initialize(), so you don't have to call it yourself anymore.
386 **/
387 if (!PyEval_ThreadsInitialized())
388 {
389 PyEval_InitThreads();
390 }
391 #endif
392
393
394 #if PY_MAJOR_VERSION == 2
395 InitializeModule();
396 #endif
397
398 // https://fr.slideshare.net/YiLungTsai/embed-python => slide 26
399 interpreterState_ = PyEval_SaveThread();
400 }
401
402
403 void PythonLock::AddSysPath(const std::string& path)
404 {
405 /**
406 * "It is recommended that applications embedding the Python
407 * interpreter for purposes other than executing a single script
408 * pass 0 as updatepath, and update sys.path themselves if
409 * desired. See CVE-2008-5983."
410 * => Set "sys.path.append()" to the directory of the configuration file by default
411 **/
412
413 PythonLock lock;
414
415 /**
416 * Initialization of "sys.path.append()" must be done before loading
417 * any module.
418 **/
419
420 PyObject *sysPath = PySys_GetObject(const_cast<char*>("path"));
421 if (sysPath == NULL)
422 {
423 OrthancPlugins::LogError("Cannot find sys.path");
424 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
425 }
426
427 PyObject* pyPath = PyUnicode_FromString(path.c_str());
428 int result = PyList_Insert(sysPath, 0, pyPath);
429 Py_DECREF(pyPath);
430
431 if (result != 0)
432 {
433 OrthancPlugins::LogError("Cannot run sys.path.append()");
434 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
435 }
436 }
437
438
439 void PythonLock::GlobalFinalize()
440 {
441 boost::mutex::scoped_lock lock(mutex_);
442
443 if (interpreterState_ != NULL)
444 {
445 PyEval_RestoreThread(interpreterState_);
446 interpreterState_ = NULL;
447 }
448
449 Py_Finalize();
450 }
451
452
453 void PythonLock::RaiseException(PyObject* module,
454 OrthancPluginErrorCode code)
455 {
456 if (code != OrthancPluginErrorCode_Success)
457 {
458 const char* message = OrthancPluginGetErrorDescription(OrthancPlugins::GetGlobalContext(), code);
459
460 struct module_state *state = GETSTATE(module);
461 if (state->exceptionClass_ == NULL)
462 {
463 OrthancPlugins::LogError("No Python exception has been registered");
464 }
465 else
466 {
467 PyErr_SetString(state->exceptionClass_, message);
468 }
469 }
470 }