0
|
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
|
13
|
50 PythonLock::PythonLock() :
|
|
51 gstate_(PyGILState_Ensure())
|
0
|
52 {
|
|
53 //OrthancPlugins::LogInfo("Python lock (GIL) acquired");
|
|
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
|
12
|
370 #if 0
|
0
|
371 #if PY_MAJOR_VERSION == 2
|
|
372 std::cout << Py_GetPath() << std::endl;
|
|
373 std::cout << Py_GetProgramName() << std::endl;
|
|
374 std::cout << Py_GetProgramFullPath() << std::endl;
|
|
375 #else
|
|
376 std::wcout << Py_GetPath() << std::endl;
|
|
377 std::wcout << Py_GetProgramName() << std::endl;
|
|
378 std::wcout << Py_GetProgramFullPath() << std::endl;
|
|
379 #endif
|
12
|
380 #endif
|
0
|
381
|
|
382 #if (PY_VERSION_HEX >= 0x03020000 /* 3.2.0 */ && \
|
|
383 PY_VERSION_HEX < 0x03070000 /* 3.7.0 */)
|
|
384 /**
|
|
385 * Changed in version 3.7: This function is now called by
|
|
386 * Py_Initialize(), so you don't have to call it yourself anymore.
|
|
387 **/
|
|
388 if (!PyEval_ThreadsInitialized())
|
|
389 {
|
|
390 PyEval_InitThreads();
|
|
391 }
|
|
392 #endif
|
|
393
|
|
394
|
|
395 #if PY_MAJOR_VERSION == 2
|
|
396 InitializeModule();
|
|
397 #endif
|
|
398
|
|
399 // https://fr.slideshare.net/YiLungTsai/embed-python => slide 26
|
|
400 interpreterState_ = PyEval_SaveThread();
|
|
401 }
|
|
402
|
|
403
|
|
404 void PythonLock::AddSysPath(const std::string& path)
|
|
405 {
|
|
406 /**
|
|
407 * "It is recommended that applications embedding the Python
|
|
408 * interpreter for purposes other than executing a single script
|
|
409 * pass 0 as updatepath, and update sys.path themselves if
|
|
410 * desired. See CVE-2008-5983."
|
|
411 * => Set "sys.path.append()" to the directory of the configuration file by default
|
|
412 **/
|
|
413
|
|
414 PythonLock lock;
|
|
415
|
|
416 /**
|
|
417 * Initialization of "sys.path.append()" must be done before loading
|
|
418 * any module.
|
|
419 **/
|
|
420
|
|
421 PyObject *sysPath = PySys_GetObject(const_cast<char*>("path"));
|
|
422 if (sysPath == NULL)
|
|
423 {
|
|
424 OrthancPlugins::LogError("Cannot find sys.path");
|
|
425 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
|
|
426 }
|
|
427
|
|
428 PyObject* pyPath = PyUnicode_FromString(path.c_str());
|
|
429 int result = PyList_Insert(sysPath, 0, pyPath);
|
|
430 Py_DECREF(pyPath);
|
|
431
|
|
432 if (result != 0)
|
|
433 {
|
|
434 OrthancPlugins::LogError("Cannot run sys.path.append()");
|
|
435 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
|
|
436 }
|
|
437 }
|
|
438
|
|
439
|
|
440 void PythonLock::GlobalFinalize()
|
|
441 {
|
|
442 boost::mutex::scoped_lock lock(mutex_);
|
|
443
|
|
444 if (interpreterState_ != NULL)
|
|
445 {
|
|
446 PyEval_RestoreThread(interpreterState_);
|
|
447 interpreterState_ = NULL;
|
|
448 }
|
|
449
|
|
450 Py_Finalize();
|
|
451 }
|
|
452
|
|
453
|
|
454 void PythonLock::RaiseException(PyObject* module,
|
|
455 OrthancPluginErrorCode code)
|
|
456 {
|
|
457 if (code != OrthancPluginErrorCode_Success)
|
|
458 {
|
|
459 const char* message = OrthancPluginGetErrorDescription(OrthancPlugins::GetGlobalContext(), code);
|
|
460
|
|
461 struct module_state *state = GETSTATE(module);
|
|
462 if (state->exceptionClass_ == NULL)
|
|
463 {
|
|
464 OrthancPlugins::LogError("No Python exception has been registered");
|
|
465 }
|
|
466 else
|
|
467 {
|
|
468 PyErr_SetString(state->exceptionClass_, message);
|
|
469 }
|
|
470 }
|
|
471 }
|