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