Commit | Line | Data |
---|---|---|
55bb57e0 | 1 | /* |
6fbd4105 | 2 | * python-plugin-provider.c |
55bb57e0 | 3 | * |
6fbd4105 | 4 | * Babeltrace Python plugin provider |
55bb57e0 PP |
5 | * |
6 | * Copyright 2017 Philippe Proulx <pproulx@efficios.com> | |
7 | * | |
8 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
9 | * of this software and associated documentation files (the "Software"), to deal | |
10 | * in the Software without restriction, including without limitation the rights | |
11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
12 | * copies of the Software, and to permit persons to whom the Software is | |
13 | * furnished to do so, subject to the following conditions: | |
14 | * | |
15 | * The above copyright notice and this permission notice shall be included in | |
16 | * all copies or substantial portions of the Software. | |
17 | * | |
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
24 | * SOFTWARE. | |
25 | */ | |
26 | ||
b03487ab | 27 | #define BT_LOG_TAG "LIB/PLUGIN-PY" |
3fe0bf43 | 28 | |
1633ef46 | 29 | #include "lib/logging.h" |
85e7137b | 30 | #include "common/macros.h" |
57952005 | 31 | #include "compat/compiler.h" |
71c5da58 | 32 | #include <babeltrace2/plugin/plugin-const.h> |
57952005 | 33 | #include "lib/plugin/plugin.h" |
71c5da58 | 34 | #include <babeltrace2/graph/component-class.h> |
a8f90e5d | 35 | #include <babeltrace2/current-thread.h> |
57952005 | 36 | #include "lib/graph/component-class.h" |
a8f90e5d | 37 | #include "py-common/py-common.h" |
55bb57e0 PP |
38 | #include <stdlib.h> |
39 | #include <signal.h> | |
40 | #include <Python.h> | |
6fbd4105 PP |
41 | #include <glib.h> |
42 | #include <gmodule.h> | |
55bb57e0 PP |
43 | |
44 | #define PYTHON_PLUGIN_FILE_PREFIX "bt_plugin_" | |
45 | #define PYTHON_PLUGIN_FILE_PREFIX_LEN (sizeof(PYTHON_PLUGIN_FILE_PREFIX) - 1) | |
46 | #define PYTHON_PLUGIN_FILE_EXT ".py" | |
47 | #define PYTHON_PLUGIN_FILE_EXT_LEN (sizeof(PYTHON_PLUGIN_FILE_EXT) - 1) | |
48 | ||
49 | enum python_state { | |
50 | /* init_python() not called yet */ | |
51 | PYTHON_STATE_NOT_INITED, | |
52 | ||
53 | /* init_python() called once with success */ | |
54 | PYTHON_STATE_FULLY_INITIALIZED, | |
55 | ||
56 | /* init_python() called once without success */ | |
57 | PYTHON_STATE_CANNOT_INITIALIZE, | |
01f50d54 PP |
58 | |
59 | /* | |
60 | * init_python() called, but environment variable asks the | |
61 | * Python interpreter not to be loaded. | |
62 | */ | |
63 | PYTHON_STATE_WONT_INITIALIZE, | |
55bb57e0 PP |
64 | } python_state = PYTHON_STATE_NOT_INITED; |
65 | ||
66 | static PyObject *py_try_load_plugin_module_func = NULL; | |
7da0994e | 67 | static bool python_was_initialized_by_us; |
55bb57e0 PP |
68 | |
69 | static | |
a8f90e5d | 70 | void append_python_traceback_error_cause(void) |
55bb57e0 | 71 | { |
a8f90e5d PP |
72 | GString *exc = NULL; |
73 | ||
74 | if (Py_IsInitialized() && PyErr_Occurred()) { | |
f33c35fb | 75 | exc = bt_py_common_format_current_exception(BT_LOG_OUTPUT_LEVEL); |
a8f90e5d PP |
76 | if (!exc) { |
77 | BT_LOGE_STR("Failed to format Python exception."); | |
78 | goto end; | |
79 | } | |
80 | ||
81 | (void) BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_UNKNOWN( | |
82 | "Babeltrace library", "%s", exc->str); | |
83 | } | |
84 | ||
85 | end: | |
86 | if (exc) { | |
87 | g_string_free(exc, TRUE); | |
88 | } | |
89 | } | |
90 | ||
91 | static | |
92 | void log_python_traceback(int log_level) | |
93 | { | |
94 | GString *exc = NULL; | |
95 | ||
96 | if (Py_IsInitialized() && PyErr_Occurred()) { | |
f33c35fb | 97 | exc = bt_py_common_format_current_exception(BT_LOG_OUTPUT_LEVEL); |
a8f90e5d PP |
98 | if (!exc) { |
99 | BT_LOGE_STR("Failed to format Python exception."); | |
100 | goto end; | |
101 | } | |
102 | ||
01f50d54 | 103 | BT_LOG_WRITE(log_level, BT_LOG_TAG, |
a8f90e5d PP |
104 | "Exception occured: Python traceback:\n%s", exc->str); |
105 | } | |
106 | ||
107 | end: | |
108 | if (exc) { | |
109 | g_string_free(exc, TRUE); | |
55bb57e0 PP |
110 | } |
111 | } | |
112 | ||
113 | static | |
114 | void pyerr_clear(void) | |
115 | { | |
116 | if (Py_IsInitialized()) { | |
117 | PyErr_Clear(); | |
118 | } | |
119 | } | |
120 | ||
121 | static | |
a8f90e5d | 122 | int init_python(void) |
55bb57e0 | 123 | { |
a8f90e5d | 124 | int ret = BT_FUNC_STATUS_OK; |
55bb57e0 PP |
125 | PyObject *py_bt2_py_plugin_mod = NULL; |
126 | const char *dis_python_env; | |
9f0d4ec8 | 127 | #ifndef __MINGW32__ |
a6b0315a | 128 | sig_t old_sigint = signal(SIGINT, SIG_DFL); |
9f0d4ec8 | 129 | #endif |
55bb57e0 | 130 | |
a8f90e5d PP |
131 | switch (python_state) { |
132 | case PYTHON_STATE_NOT_INITED: | |
133 | break; | |
134 | case PYTHON_STATE_FULLY_INITIALIZED: | |
135 | goto end; | |
136 | case PYTHON_STATE_WONT_INITIALIZE: | |
137 | ret = BT_FUNC_STATUS_NOT_FOUND; | |
55bb57e0 | 138 | goto end; |
a8f90e5d PP |
139 | case PYTHON_STATE_CANNOT_INITIALIZE: |
140 | ret = BT_FUNC_STATUS_ERROR; | |
141 | goto end; | |
142 | default: | |
143 | abort(); | |
55bb57e0 PP |
144 | } |
145 | ||
146 | /* | |
147 | * User can disable Python plugin support with the | |
56528113 PP |
148 | * `LIBBABELTRACE2_DISABLE_PYTHON_PLUGINS` environment variable |
149 | * set to 1. | |
55bb57e0 | 150 | */ |
56528113 | 151 | dis_python_env = getenv("LIBBABELTRACE2_DISABLE_PYTHON_PLUGINS"); |
9e0bf9b0 | 152 | if (dis_python_env && strcmp(dis_python_env, "1") == 0) { |
56528113 PP |
153 | BT_LOGI_STR("Python plugin support is disabled because the " |
154 | "`LIBBABELTRACE2_DISABLE_PYTHON_PLUGINS` environment " | |
155 | "variable is set to `1`."); | |
01f50d54 | 156 | python_state = PYTHON_STATE_WONT_INITIALIZE; |
a8f90e5d | 157 | ret = BT_FUNC_STATUS_NOT_FOUND; |
55bb57e0 PP |
158 | goto end; |
159 | } | |
160 | ||
161 | if (!Py_IsInitialized()) { | |
53264927 | 162 | BT_LOGI_STR("Python interpreter is not initialized: initializing Python interpreter."); |
55bb57e0 | 163 | Py_InitializeEx(0); |
7da0994e | 164 | python_was_initialized_by_us = true; |
9e0bf9b0 PP |
165 | BT_LOGI("Initialized Python interpreter: version=\"%s\"", |
166 | Py_GetVersion()); | |
53264927 PP |
167 | } else { |
168 | BT_LOGI("Python interpreter is already initialized: version=\"%s\"", | |
169 | Py_GetVersion()); | |
55bb57e0 PP |
170 | } |
171 | ||
172 | py_bt2_py_plugin_mod = PyImport_ImportModule("bt2.py_plugin"); | |
173 | if (!py_bt2_py_plugin_mod) { | |
a8f90e5d PP |
174 | append_python_traceback_error_cause(); |
175 | BT_LIB_LOGW_APPEND_CAUSE( | |
176 | "Cannot import `bt2.py_plugin` Python module: " | |
177 | "Python plugin support is disabled."); | |
55bb57e0 | 178 | python_state = PYTHON_STATE_CANNOT_INITIALIZE; |
a8f90e5d | 179 | ret = BT_FUNC_STATUS_ERROR; |
55bb57e0 PP |
180 | goto end; |
181 | } | |
182 | ||
183 | py_try_load_plugin_module_func = | |
184 | PyObject_GetAttrString(py_bt2_py_plugin_mod, "_try_load_plugin_module"); | |
185 | if (!py_try_load_plugin_module_func) { | |
a8f90e5d PP |
186 | append_python_traceback_error_cause(); |
187 | BT_LIB_LOGW_APPEND_CAUSE( | |
188 | "Cannot get `_try_load_plugin_module` attribute from `bt2.py_plugin` Python module: " | |
189 | "Python plugin support is disabled."); | |
55bb57e0 | 190 | python_state = PYTHON_STATE_CANNOT_INITIALIZE; |
a8f90e5d | 191 | ret = BT_FUNC_STATUS_ERROR; |
55bb57e0 PP |
192 | goto end; |
193 | } | |
194 | ||
195 | python_state = PYTHON_STATE_FULLY_INITIALIZED; | |
196 | ||
197 | end: | |
9f0d4ec8 | 198 | #ifndef __MINGW32__ |
55bb57e0 PP |
199 | if (old_sigint != SIG_ERR) { |
200 | (void) signal(SIGINT, old_sigint); | |
201 | } | |
9f0d4ec8 | 202 | #endif |
55bb57e0 | 203 | |
a8f90e5d | 204 | log_python_traceback(ret == BT_FUNC_STATUS_ERROR ? |
e9d0e821 | 205 | BT_LOG_WARNING : BT_LOG_INFO); |
55bb57e0 PP |
206 | pyerr_clear(); |
207 | Py_XDECREF(py_bt2_py_plugin_mod); | |
a8f90e5d | 208 | return ret; |
55bb57e0 PP |
209 | } |
210 | ||
211 | __attribute__((destructor)) static | |
212 | void fini_python(void) { | |
7da0994e | 213 | if (Py_IsInitialized() && python_was_initialized_by_us) { |
55bb57e0 PP |
214 | if (py_try_load_plugin_module_func) { |
215 | Py_DECREF(py_try_load_plugin_module_func); | |
216 | py_try_load_plugin_module_func = NULL; | |
217 | } | |
218 | ||
219 | Py_Finalize(); | |
9e0bf9b0 | 220 | BT_LOGI_STR("Finalized Python interpreter."); |
55bb57e0 PP |
221 | } |
222 | ||
223 | python_state = PYTHON_STATE_NOT_INITED; | |
224 | } | |
225 | ||
226 | static | |
fb25b9e3 | 227 | int bt_plugin_from_python_plugin_info(PyObject *plugin_info, |
01f50d54 | 228 | bool fail_on_load_error, bt_plugin **plugin_out) |
55bb57e0 | 229 | { |
a8f90e5d | 230 | int status = BT_FUNC_STATUS_OK; |
55bb57e0 PP |
231 | PyObject *py_name = NULL; |
232 | PyObject *py_author = NULL; | |
233 | PyObject *py_description = NULL; | |
234 | PyObject *py_license = NULL; | |
235 | PyObject *py_version = NULL; | |
236 | PyObject *py_comp_class_addrs = NULL; | |
237 | const char *name = NULL; | |
238 | const char *author = NULL; | |
239 | const char *description = NULL; | |
240 | const char *license = NULL; | |
241 | unsigned int major = 0, minor = 0, patch = 0; | |
242 | const char *version_extra = NULL; | |
55bb57e0 | 243 | |
01f50d54 PP |
244 | BT_ASSERT(plugin_out); |
245 | *plugin_out = NULL; | |
8b45963b PP |
246 | BT_ASSERT(plugin_info); |
247 | BT_ASSERT(python_state == PYTHON_STATE_FULLY_INITIALIZED); | |
55bb57e0 PP |
248 | py_name = PyObject_GetAttrString(plugin_info, "name"); |
249 | if (!py_name) { | |
a8f90e5d PP |
250 | if (fail_on_load_error) { |
251 | append_python_traceback_error_cause(); | |
252 | BT_LIB_LOGW_APPEND_CAUSE( | |
253 | "Cannot find `name` attribute in Python plugin info object: " | |
254 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 255 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 256 | } else { |
6b076590 | 257 | BT_LIB_LOGW( |
a8f90e5d PP |
258 | "Cannot find `name` attribute in Python plugin info object: " |
259 | "py-plugin-info-addr=%p", plugin_info); | |
260 | status = BT_FUNC_STATUS_NOT_FOUND; | |
261 | } | |
262 | ||
55bb57e0 PP |
263 | goto error; |
264 | } | |
265 | ||
266 | py_author = PyObject_GetAttrString(plugin_info, "author"); | |
267 | if (!py_author) { | |
a8f90e5d PP |
268 | if (fail_on_load_error) { |
269 | append_python_traceback_error_cause(); | |
270 | BT_LIB_LOGW_APPEND_CAUSE( | |
271 | "Cannot find `author` attribute in Python plugin info object: " | |
272 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 273 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 274 | } else { |
6b076590 | 275 | BT_LIB_LOGW( |
a8f90e5d PP |
276 | "Cannot find `author` attribute in Python plugin info object: " |
277 | "py-plugin-info-addr=%p", plugin_info); | |
278 | status = BT_FUNC_STATUS_NOT_FOUND; | |
279 | } | |
280 | ||
55bb57e0 PP |
281 | goto error; |
282 | } | |
283 | ||
284 | py_description = PyObject_GetAttrString(plugin_info, "description"); | |
285 | if (!py_description) { | |
a8f90e5d PP |
286 | if (fail_on_load_error) { |
287 | append_python_traceback_error_cause(); | |
288 | BT_LIB_LOGW_APPEND_CAUSE( | |
289 | "Cannot find `description` attribute in Python plugin info object: " | |
290 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 291 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 292 | } else { |
6b076590 | 293 | BT_LIB_LOGW( |
a8f90e5d PP |
294 | "Cannot find `description` attribute in Python plugin info object: " |
295 | "py-plugin-info-addr=%p", plugin_info); | |
296 | status = BT_FUNC_STATUS_NOT_FOUND; | |
297 | } | |
298 | ||
55bb57e0 PP |
299 | goto error; |
300 | } | |
301 | ||
302 | py_license = PyObject_GetAttrString(plugin_info, "license"); | |
303 | if (!py_license) { | |
a8f90e5d PP |
304 | if (fail_on_load_error) { |
305 | append_python_traceback_error_cause(); | |
306 | BT_LIB_LOGW_APPEND_CAUSE( | |
307 | "Cannot find `license` attribute in Python plugin info object: " | |
308 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 309 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 310 | } else { |
6b076590 | 311 | BT_LIB_LOGW( |
a8f90e5d PP |
312 | "Cannot find `license` attribute in Python plugin info object: " |
313 | "py-plugin-info-addr=%p", plugin_info); | |
314 | status = BT_FUNC_STATUS_NOT_FOUND; | |
315 | } | |
316 | ||
55bb57e0 PP |
317 | goto error; |
318 | } | |
319 | ||
320 | py_version = PyObject_GetAttrString(plugin_info, "version"); | |
321 | if (!py_version) { | |
a8f90e5d PP |
322 | if (fail_on_load_error) { |
323 | append_python_traceback_error_cause(); | |
324 | BT_LIB_LOGW_APPEND_CAUSE( | |
325 | "Cannot find `version` attribute in Python plugin info object: " | |
326 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 327 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 328 | } else { |
6b076590 | 329 | BT_LIB_LOGW( |
a8f90e5d PP |
330 | "Cannot find `version` attribute in Python plugin info object: " |
331 | "py-plugin-info-addr=%p", plugin_info); | |
332 | status = BT_FUNC_STATUS_NOT_FOUND; | |
333 | } | |
334 | ||
55bb57e0 PP |
335 | goto error; |
336 | } | |
337 | ||
338 | py_comp_class_addrs = PyObject_GetAttrString(plugin_info, | |
339 | "comp_class_addrs"); | |
340 | if (!py_comp_class_addrs) { | |
a8f90e5d PP |
341 | if (fail_on_load_error) { |
342 | append_python_traceback_error_cause(); | |
343 | BT_LIB_LOGW_APPEND_CAUSE( | |
344 | "Cannot find `comp_class_addrs` attribute in Python plugin info object: " | |
345 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 346 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 347 | } else { |
6b076590 | 348 | BT_LIB_LOGW( |
a8f90e5d PP |
349 | "Cannot find `comp_class_addrs` attribute in Python plugin info object: " |
350 | "py-plugin-info-addr=%p", plugin_info); | |
351 | status = BT_FUNC_STATUS_NOT_FOUND; | |
352 | } | |
353 | ||
55bb57e0 PP |
354 | goto error; |
355 | } | |
356 | ||
357 | if (PyUnicode_Check(py_name)) { | |
358 | name = PyUnicode_AsUTF8(py_name); | |
359 | if (!name) { | |
a8f90e5d PP |
360 | if (fail_on_load_error) { |
361 | append_python_traceback_error_cause(); | |
362 | BT_LIB_LOGW_APPEND_CAUSE( | |
363 | "Cannot decode Python plugin name string: " | |
364 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 365 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 366 | } else { |
6b076590 | 367 | BT_LIB_LOGW( |
a8f90e5d PP |
368 | "Cannot decode Python plugin name string: " |
369 | "py-plugin-info-addr=%p", plugin_info); | |
370 | status = BT_FUNC_STATUS_NOT_FOUND; | |
371 | } | |
372 | ||
55bb57e0 PP |
373 | goto error; |
374 | } | |
375 | } else { | |
376 | /* Plugin name is mandatory */ | |
a8f90e5d PP |
377 | if (fail_on_load_error) { |
378 | append_python_traceback_error_cause(); | |
379 | BT_LIB_LOGW_APPEND_CAUSE( | |
380 | "Plugin name is not a string: " | |
381 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 382 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 383 | } else { |
6b076590 | 384 | BT_LIB_LOGW( |
a8f90e5d PP |
385 | "Plugin name is not a string: " |
386 | "py-plugin-info-addr=%p", plugin_info); | |
387 | status = BT_FUNC_STATUS_NOT_FOUND; | |
388 | } | |
389 | ||
55bb57e0 PP |
390 | goto error; |
391 | } | |
392 | ||
393 | if (PyUnicode_Check(py_author)) { | |
394 | author = PyUnicode_AsUTF8(py_author); | |
395 | if (!author) { | |
a8f90e5d PP |
396 | if (fail_on_load_error) { |
397 | append_python_traceback_error_cause(); | |
398 | BT_LIB_LOGW_APPEND_CAUSE( | |
399 | "Cannot decode Python plugin author string: " | |
400 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 401 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 402 | } else { |
6b076590 | 403 | BT_LIB_LOGW( |
a8f90e5d PP |
404 | "Cannot decode Python plugin author string: " |
405 | "py-plugin-info-addr=%p", plugin_info); | |
406 | status = BT_FUNC_STATUS_NOT_FOUND; | |
407 | } | |
408 | ||
55bb57e0 PP |
409 | goto error; |
410 | } | |
411 | } | |
412 | ||
413 | if (PyUnicode_Check(py_description)) { | |
414 | description = PyUnicode_AsUTF8(py_description); | |
415 | if (!description) { | |
a8f90e5d PP |
416 | if (fail_on_load_error) { |
417 | append_python_traceback_error_cause(); | |
418 | BT_LIB_LOGW_APPEND_CAUSE( | |
419 | "Cannot decode Python plugin description string: " | |
420 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 421 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 422 | } else { |
6b076590 | 423 | BT_LIB_LOGW( |
a8f90e5d PP |
424 | "Cannot decode Python plugin description string: " |
425 | "py-plugin-info-addr=%p", plugin_info); | |
426 | status = BT_FUNC_STATUS_NOT_FOUND; | |
427 | } | |
428 | ||
55bb57e0 PP |
429 | goto error; |
430 | } | |
431 | } | |
432 | ||
433 | if (PyUnicode_Check(py_license)) { | |
434 | license = PyUnicode_AsUTF8(py_license); | |
435 | if (!license) { | |
a8f90e5d PP |
436 | if (fail_on_load_error) { |
437 | append_python_traceback_error_cause(); | |
438 | BT_LIB_LOGW_APPEND_CAUSE( | |
439 | "Cannot decode Python plugin license string: " | |
440 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 441 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 442 | } else { |
6b076590 | 443 | BT_LIB_LOGW( |
a8f90e5d PP |
444 | "Cannot decode Python plugin license string: " |
445 | "py-plugin-info-addr=%p", plugin_info); | |
446 | status = BT_FUNC_STATUS_NOT_FOUND; | |
447 | } | |
448 | ||
55bb57e0 PP |
449 | goto error; |
450 | } | |
451 | } | |
452 | ||
453 | if (PyTuple_Check(py_version)) { | |
454 | if (PyTuple_Size(py_version) >= 3) { | |
455 | PyObject *py_major = PyTuple_GetItem(py_version, 0); | |
456 | PyObject *py_minor = PyTuple_GetItem(py_version, 1); | |
457 | PyObject *py_patch = PyTuple_GetItem(py_version, 2); | |
458 | ||
8b45963b PP |
459 | BT_ASSERT(py_major); |
460 | BT_ASSERT(py_minor); | |
461 | BT_ASSERT(py_patch); | |
55bb57e0 PP |
462 | |
463 | if (PyLong_Check(py_major)) { | |
464 | major = PyLong_AsUnsignedLong(py_major); | |
465 | } | |
466 | ||
467 | if (PyLong_Check(py_minor)) { | |
468 | minor = PyLong_AsUnsignedLong(py_minor); | |
469 | } | |
470 | ||
471 | if (PyLong_Check(py_patch)) { | |
472 | patch = PyLong_AsUnsignedLong(py_patch); | |
473 | } | |
474 | ||
475 | if (PyErr_Occurred()) { | |
476 | /* Overflow error, most probably */ | |
a8f90e5d PP |
477 | if (fail_on_load_error) { |
478 | append_python_traceback_error_cause(); | |
479 | BT_LIB_LOGW_APPEND_CAUSE( | |
480 | "Invalid Python plugin version format: " | |
481 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 482 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 483 | } else { |
6b076590 | 484 | BT_LIB_LOGW( |
a8f90e5d PP |
485 | "Invalid Python plugin version format: " |
486 | "py-plugin-info-addr=%p", plugin_info); | |
487 | status = BT_FUNC_STATUS_NOT_FOUND; | |
488 | } | |
489 | ||
55bb57e0 PP |
490 | goto error; |
491 | } | |
492 | } | |
493 | ||
494 | if (PyTuple_Size(py_version) >= 4) { | |
495 | PyObject *py_extra = PyTuple_GetItem(py_version, 3); | |
496 | ||
8b45963b | 497 | BT_ASSERT(py_extra); |
55bb57e0 PP |
498 | |
499 | if (PyUnicode_Check(py_extra)) { | |
500 | version_extra = PyUnicode_AsUTF8(py_extra); | |
501 | if (!version_extra) { | |
a8f90e5d PP |
502 | if (fail_on_load_error) { |
503 | append_python_traceback_error_cause(); | |
504 | BT_LIB_LOGW_APPEND_CAUSE( | |
505 | "Cannot decode Python plugin version's extra string: " | |
506 | "py-plugin-info-addr=%p", plugin_info); | |
c24a8f3c | 507 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 508 | } else { |
6b076590 | 509 | BT_LIB_LOGW( |
a8f90e5d PP |
510 | "Cannot decode Python plugin version's extra string: " |
511 | "py-plugin-info-addr=%p", plugin_info); | |
512 | status = BT_FUNC_STATUS_NOT_FOUND; | |
513 | } | |
514 | ||
55bb57e0 PP |
515 | goto error; |
516 | } | |
517 | } | |
518 | } | |
519 | } | |
520 | ||
01f50d54 PP |
521 | *plugin_out = bt_plugin_create_empty(BT_PLUGIN_TYPE_PYTHON); |
522 | if (!*plugin_out) { | |
a8f90e5d PP |
523 | BT_LIB_LOGE_APPEND_CAUSE("Cannot create empty plugin object."); |
524 | status = BT_FUNC_STATUS_MEMORY_ERROR; | |
55bb57e0 PP |
525 | goto error; |
526 | } | |
527 | ||
01f50d54 | 528 | bt_plugin_set_name(*plugin_out, name); |
55bb57e0 PP |
529 | |
530 | if (description) { | |
01f50d54 | 531 | bt_plugin_set_description(*plugin_out, description); |
55bb57e0 PP |
532 | } |
533 | ||
534 | if (author) { | |
01f50d54 | 535 | bt_plugin_set_author(*plugin_out, author); |
55bb57e0 PP |
536 | } |
537 | ||
538 | if (license) { | |
01f50d54 | 539 | bt_plugin_set_license(*plugin_out, license); |
55bb57e0 PP |
540 | } |
541 | ||
01f50d54 | 542 | bt_plugin_set_version(*plugin_out, major, minor, patch, version_extra); |
55bb57e0 PP |
543 | |
544 | if (PyList_Check(py_comp_class_addrs)) { | |
545 | size_t i; | |
546 | ||
547 | for (i = 0; i < PyList_Size(py_comp_class_addrs); i++) { | |
8eee8ea2 | 548 | bt_component_class *comp_class; |
55bb57e0 PP |
549 | PyObject *py_comp_class_addr; |
550 | ||
551 | py_comp_class_addr = | |
552 | PyList_GetItem(py_comp_class_addrs, i); | |
8b45963b | 553 | BT_ASSERT(py_comp_class_addr); |
55bb57e0 | 554 | if (PyLong_Check(py_comp_class_addr)) { |
c240a5d8 | 555 | comp_class = PyLong_AsVoidPtr(py_comp_class_addr); |
55bb57e0 | 556 | } else { |
01f50d54 | 557 | if (fail_on_load_error) { |
a8f90e5d PP |
558 | append_python_traceback_error_cause(); |
559 | BT_LIB_LOGW_APPEND_CAUSE( | |
560 | "Component class address is not an integer in Python plugin info object: " | |
561 | "py-plugin-info-addr=%p, index=%zu", | |
562 | plugin_info, i); | |
c24a8f3c | 563 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 564 | } else { |
6b076590 | 565 | BT_LIB_LOGW( |
a8f90e5d PP |
566 | "Component class address is not an integer in Python plugin info object: " |
567 | "py-plugin-info-addr=%p, index=%zu", | |
568 | plugin_info, i); | |
569 | status = BT_FUNC_STATUS_NOT_FOUND; | |
01f50d54 PP |
570 | } |
571 | ||
55bb57e0 PP |
572 | continue; |
573 | } | |
574 | ||
01f50d54 PP |
575 | status = bt_plugin_add_component_class(*plugin_out, |
576 | comp_class); | |
577 | if (status < 0) { | |
a8f90e5d PP |
578 | BT_LIB_LOGE_APPEND_CAUSE( |
579 | "Cannot add component class to plugin: " | |
9e0bf9b0 PP |
580 | "py-plugin-info-addr=%p, " |
581 | "plugin-addr=%p, plugin-name=\"%s\", " | |
582 | "comp-class-addr=%p, " | |
583 | "comp-class-name=\"%s\", " | |
584 | "comp-class-type=%s", | |
01f50d54 PP |
585 | plugin_info, *plugin_out, |
586 | bt_plugin_get_name(*plugin_out), | |
9e0bf9b0 PP |
587 | comp_class, |
588 | bt_component_class_get_name(comp_class), | |
589 | bt_component_class_type_string( | |
590 | bt_component_class_get_type(comp_class))); | |
01f50d54 | 591 | goto error; |
55bb57e0 PP |
592 | } |
593 | } | |
594 | } | |
595 | ||
55bb57e0 PP |
596 | goto end; |
597 | ||
598 | error: | |
a8f90e5d | 599 | BT_ASSERT(status != BT_FUNC_STATUS_OK); |
e9d0e821 | 600 | log_python_traceback(fail_on_load_error ? BT_LOG_WARNING : BT_LOG_INFO); |
55bb57e0 | 601 | pyerr_clear(); |
01f50d54 | 602 | BT_OBJECT_PUT_REF_AND_RESET(*plugin_out); |
55bb57e0 PP |
603 | |
604 | end: | |
605 | Py_XDECREF(py_name); | |
606 | Py_XDECREF(py_author); | |
607 | Py_XDECREF(py_description); | |
608 | Py_XDECREF(py_license); | |
609 | Py_XDECREF(py_version); | |
610 | Py_XDECREF(py_comp_class_addrs); | |
01f50d54 | 611 | return status; |
55bb57e0 PP |
612 | } |
613 | ||
6fbd4105 | 614 | G_MODULE_EXPORT |
fb25b9e3 | 615 | int bt_plugin_python_create_all_from_file(const char *path, |
01f50d54 | 616 | bool fail_on_load_error, struct bt_plugin_set **plugin_set_out) |
55bb57e0 | 617 | { |
09fc237b | 618 | bt_plugin *plugin = NULL; |
55bb57e0 PP |
619 | PyObject *py_plugin_info = NULL; |
620 | gchar *basename = NULL; | |
621 | size_t path_len; | |
a8f90e5d | 622 | int status = BT_FUNC_STATUS_OK; |
55bb57e0 | 623 | |
8b45963b | 624 | BT_ASSERT(path); |
55bb57e0 PP |
625 | |
626 | if (python_state == PYTHON_STATE_CANNOT_INITIALIZE) { | |
627 | /* | |
628 | * We do not even care about the rest of the function | |
629 | * here because we already know Python cannot be fully | |
630 | * initialized. | |
631 | */ | |
a8f90e5d PP |
632 | BT_LIB_LOGE_APPEND_CAUSE( |
633 | "Python interpreter could not be initialized previously."); | |
634 | status = BT_FUNC_STATUS_ERROR; | |
01f50d54 PP |
635 | goto error; |
636 | } else if (python_state == PYTHON_STATE_WONT_INITIALIZE) { | |
637 | /* | |
638 | * This is not an error: the environment requires that | |
639 | * Python plugins are disabled, so it's simply not | |
640 | * found. | |
641 | */ | |
56528113 PP |
642 | BT_LOGI_STR("Python plugin support was disabled previously " |
643 | "because the `LIBBABELTRACE2_DISABLE_PYTHON_PLUGINS` " | |
644 | "environment variable is set to `1`."); | |
a8f90e5d | 645 | status = BT_FUNC_STATUS_NOT_FOUND; |
55bb57e0 PP |
646 | goto error; |
647 | } | |
648 | ||
a684a357 PP |
649 | BT_LOGI("Trying to create all Python plugins from file: path=\"%s\"", |
650 | path); | |
55bb57e0 PP |
651 | path_len = strlen(path); |
652 | ||
653 | /* File name ends with `.py` */ | |
654 | if (strncmp(path + path_len - PYTHON_PLUGIN_FILE_EXT_LEN, | |
655 | PYTHON_PLUGIN_FILE_EXT, | |
656 | PYTHON_PLUGIN_FILE_EXT_LEN) != 0) { | |
a684a357 | 657 | BT_LOGI("Skipping non-Python file: path=\"%s\"", path); |
a8f90e5d | 658 | status = BT_FUNC_STATUS_NOT_FOUND; |
55bb57e0 PP |
659 | goto error; |
660 | } | |
661 | ||
662 | /* File name starts with `bt_plugin_` */ | |
663 | basename = g_path_get_basename(path); | |
664 | if (!basename) { | |
a8f90e5d PP |
665 | BT_LIB_LOGE_APPEND_CAUSE( |
666 | "Cannot get path's basename: path=\"%s\"", path); | |
667 | status = BT_FUNC_STATUS_ERROR; | |
55bb57e0 PP |
668 | goto error; |
669 | } | |
670 | ||
671 | if (strncmp(basename, PYTHON_PLUGIN_FILE_PREFIX, | |
672 | PYTHON_PLUGIN_FILE_PREFIX_LEN) != 0) { | |
a684a357 | 673 | BT_LOGI("Skipping Python file not starting with `%s`: " |
9e0bf9b0 | 674 | "path=\"%s\"", PYTHON_PLUGIN_FILE_PREFIX, path); |
a8f90e5d | 675 | status = BT_FUNC_STATUS_NOT_FOUND; |
55bb57e0 PP |
676 | goto error; |
677 | } | |
678 | ||
679 | /* | |
680 | * Initialize Python now. | |
681 | * | |
682 | * This is not done in the library contructor because the | |
683 | * interpreter is somewhat slow to initialize. If you don't | |
684 | * have any potential Python plugins, you don't need to endure | |
685 | * this waiting time everytime you load the library. | |
686 | */ | |
a8f90e5d PP |
687 | status = init_python(); |
688 | if (status != BT_FUNC_STATUS_OK) { | |
689 | /* init_python() logs and append errors */ | |
55bb57e0 PP |
690 | goto error; |
691 | } | |
692 | ||
693 | /* | |
694 | * Call bt2.py_plugin._try_load_plugin_module() with this path | |
695 | * to get plugin info if the plugin is loadable and complete. | |
696 | * This function returns None when there's an error, but just in | |
697 | * case we also manually clear the last Python error state. | |
698 | */ | |
9e0bf9b0 | 699 | BT_LOGD_STR("Getting Python plugin info object from Python module."); |
55bb57e0 PP |
700 | py_plugin_info = PyObject_CallFunction(py_try_load_plugin_module_func, |
701 | "(s)", path); | |
702 | if (!py_plugin_info || py_plugin_info == Py_None) { | |
a8f90e5d PP |
703 | if (fail_on_load_error) { |
704 | append_python_traceback_error_cause(); | |
705 | BT_LIB_LOGW_APPEND_CAUSE( | |
706 | "Cannot load Python plugin: path=\"%s\"", path); | |
c24a8f3c | 707 | status = BT_FUNC_STATUS_ERROR; |
a8f90e5d | 708 | } else { |
6b076590 | 709 | BT_LIB_LOGW( |
a8f90e5d PP |
710 | "Cannot load Python plugin: path=\"%s\"", path); |
711 | status = BT_FUNC_STATUS_NOT_FOUND; | |
712 | } | |
713 | ||
55bb57e0 PP |
714 | goto error; |
715 | } | |
716 | ||
717 | /* | |
718 | * Get bt_plugin from plugin info object. | |
55bb57e0 | 719 | */ |
01f50d54 PP |
720 | plugin = NULL; |
721 | status = bt_plugin_from_python_plugin_info(py_plugin_info, | |
722 | fail_on_load_error, &plugin); | |
723 | if (status < 0) { | |
724 | /* | |
725 | * bt_plugin_from_python_plugin_info() handles | |
726 | * `fail_on_load_error`, so this is a "real" error. | |
727 | */ | |
a8f90e5d PP |
728 | BT_LIB_LOGW_APPEND_CAUSE( |
729 | "Cannot create plugin object from Python plugin info object: " | |
9e0bf9b0 PP |
730 | "path=\"%s\", py-plugin-info-addr=%p", |
731 | path, py_plugin_info); | |
01f50d54 PP |
732 | BT_ASSERT(!plugin); |
733 | goto error; | |
a8f90e5d | 734 | } else if (status == BT_FUNC_STATUS_NOT_FOUND) { |
01f50d54 | 735 | BT_ASSERT(!plugin); |
55bb57e0 PP |
736 | goto error; |
737 | } | |
738 | ||
a8f90e5d | 739 | BT_ASSERT(status == BT_FUNC_STATUS_OK); |
01f50d54 | 740 | BT_ASSERT(plugin); |
a8ff38ef | 741 | bt_plugin_set_path(plugin, path); |
01f50d54 PP |
742 | *plugin_set_out = bt_plugin_set_create(); |
743 | if (!*plugin_set_out) { | |
a8f90e5d PP |
744 | BT_LIB_LOGE_APPEND_CAUSE("Cannot create empty plugin set."); |
745 | status = BT_FUNC_STATUS_MEMORY_ERROR; | |
55bb57e0 PP |
746 | goto error; |
747 | } | |
748 | ||
01f50d54 | 749 | bt_plugin_set_add_plugin(*plugin_set_out, plugin); |
9e0bf9b0 PP |
750 | BT_LOGD("Created all Python plugins from file: path=\"%s\", " |
751 | "plugin-addr=%p, plugin-name=\"%s\"", | |
752 | path, plugin, bt_plugin_get_name(plugin)); | |
55bb57e0 PP |
753 | goto end; |
754 | ||
755 | error: | |
a8f90e5d | 756 | BT_ASSERT(status != BT_FUNC_STATUS_OK); |
6b076590 | 757 | log_python_traceback(BT_LOG_WARNING); |
a8f90e5d | 758 | pyerr_clear(); |
01f50d54 | 759 | BT_OBJECT_PUT_REF_AND_RESET(*plugin_set_out); |
55bb57e0 PP |
760 | |
761 | end: | |
8c6884d9 | 762 | bt_plugin_put_ref(plugin); |
55bb57e0 | 763 | Py_XDECREF(py_plugin_info); |
a8f90e5d | 764 | |
be1b2e7e | 765 | g_free(basename); |
a8f90e5d | 766 | |
01f50d54 | 767 | return status; |
55bb57e0 | 768 | } |