1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2011-2014 Planets Communications B.V.
5 Copyright (C) 2013-2021 Bareos GmbH & Co. KG
6
7 This program is Free Software; you can redistribute it and/or
8 modify it under the terms of version three of the GNU Affero General Public
9 License as published by the Free Software Foundation, which is
10 listed in the file LICENSE.
11
12 This program is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Affero General Public License for more details.
16
17 You should have received a copy of the GNU Affero General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 02110-1301, USA.
21 */
22 /**
23 * @file
24 * Python plugin for the Bareos Storage Daemon
25 */
26 #define PY_SSIZE_T_CLEAN
27 #define BUILD_PLUGIN
28
29 #if defined(HAVE_WIN32)
30 # include "include/bareos.h"
31 # include <Python.h>
32 #else
33 # include <Python.h>
34 # include "include/bareos.h"
35 #endif
36 #include "include/version_hex.h"
37
38 #define PLUGIN_DAEMON "sd"
39
40 #if PY_VERSION_HEX < VERSION_HEX(3, 0, 0)
41 # define PLUGIN_NAME "python"
42 # define PLUGIN_DIR PY2MODDIR
43 #else
44 # define PLUGIN_NAME "python3"
45 # define PLUGIN_DIR PY3MODDIR
46 #endif
47
48 #define LOGPREFIX PLUGIN_NAME "-" PLUGIN_DAEMON ": "
49
50 #include "stored/stored.h"
51 #include "plugins/include/python3compat.h"
52
53 #include "python-sd.h"
54 #include "module/bareossd.h"
55 #include "lib/edit.h"
56
57 namespace storagedaemon {
58
59 static const int debuglevel = 150;
60
61 #define PLUGIN_LICENSE "Bareos AGPLv3"
62 #define PLUGIN_AUTHOR "Bareos GmbH & Co.KG"
63 #define PLUGIN_DATE "May 2020"
64 #define PLUGIN_VERSION "4"
65 #define PLUGIN_DESCRIPTION "Python Storage Daemon Plugin"
66 #define PLUGIN_USAGE \
67 PLUGIN_NAME \
68 ":instance=<instance_id>:module_path=<path-to-python-modules>:module_" \
69 "name=<python-module-to-load>"
70
71
72 /* Forward referenced functions */
73 static bRC newPlugin(PluginContext* plugin_ctx);
74 static bRC freePlugin(PluginContext* plugin_ctx);
75 static bRC getPluginValue(PluginContext* plugin_ctx,
76 pVariable var,
77 void* value);
78 static bRC setPluginValue(PluginContext* plugin_ctx,
79 pVariable var,
80 void* value);
81 static bRC handlePluginEvent(PluginContext* plugin_ctx,
82 bSdEvent* event,
83 void* value);
84 static bRC parse_plugin_definition(PluginContext* plugin_ctx,
85 void* value,
86 PoolMem& plugin_options);
87
88 static void PyErrorHandler(PluginContext* plugin_ctx, int msgtype);
89 static bRC PyLoadModule(PluginContext* plugin_ctx, void* value);
90
91 /* Pointers to Bareos functions */
92 static CoreFunctions* bareos_core_functions = NULL;
93 static PluginApiDefinition* bareos_plugin_interface_version = NULL;
94
95 static PluginInformation pluginInfo
96 = {sizeof(pluginInfo), SD_PLUGIN_INTERFACE_VERSION,
97 SD_PLUGIN_MAGIC, PLUGIN_LICENSE,
98 PLUGIN_AUTHOR, PLUGIN_DATE,
99 PLUGIN_VERSION, PLUGIN_DESCRIPTION,
100 PLUGIN_USAGE};
101
102 static PluginFunctions pluginFuncs
103 = {sizeof(pluginFuncs), SD_PLUGIN_INTERFACE_VERSION,
104
105 /* Entry points into plugin */
106 newPlugin, /* new plugin instance */
107 freePlugin, /* free plugin instance */
108 getPluginValue, setPluginValue, handlePluginEvent};
109
110 #include "plugin_private_context.h"
111
112
113 /**
114 * We don't actually use this but we need it to tear down the
115 * final python interpreter on unload of the plugin. Each instance of
116 * the plugin get its own interpreter.
117 */
118 static PyThreadState* mainThreadState{nullptr};
119
120 /* functions common to all plugins */
121 #include "plugins/include/python_plugins_common.inc"
122
123 /* Common functions used in all python plugins. */
getPluginValue(PluginContext * bareos_plugin_ctx,pVariable var,void * value)124 static bRC getPluginValue(PluginContext* bareos_plugin_ctx,
125 pVariable var,
126 void* value)
127 {
128 struct plugin_private_context* plugin_priv_ctx
129 = (struct plugin_private_context*)
130 bareos_plugin_ctx->plugin_private_context;
131 bRC retval = bRC_Error;
132
133 if (!plugin_priv_ctx) { goto bail_out; }
134
135 PyEval_AcquireThread(plugin_priv_ctx->interpreter);
136 retval = Bareossd_PyGetPluginValue(bareos_plugin_ctx, var, value);
137 PyEval_ReleaseThread(plugin_priv_ctx->interpreter);
138
139 bail_out:
140 return retval;
141 }
142
setPluginValue(PluginContext * bareos_plugin_ctx,pVariable var,void * value)143 static bRC setPluginValue(PluginContext* bareos_plugin_ctx,
144 pVariable var,
145 void* value)
146 {
147 struct plugin_private_context* plugin_priv_ctx
148 = (struct plugin_private_context*)
149 bareos_plugin_ctx->plugin_private_context;
150 bRC retval = bRC_Error;
151
152 if (!plugin_priv_ctx) { return bRC_Error; }
153
154 PyEval_AcquireThread(plugin_priv_ctx->interpreter);
155 retval = Bareossd_PySetPluginValue(bareos_plugin_ctx, var, value);
156 PyEval_ReleaseThread(plugin_priv_ctx->interpreter);
157
158 return retval;
159 }
160
161
162 #ifdef __cplusplus
163 extern "C" {
164 #endif
165
PyErrorHandler()166 static void PyErrorHandler()
167 {
168 PyObject *type, *value, *traceback;
169 PyObject* tracebackModule;
170 char* error_string;
171
172 PyErr_Fetch(&type, &value, &traceback);
173 PyErr_NormalizeException(&type, &value, &traceback);
174
175 tracebackModule = PyImport_ImportModule("traceback");
176 if (tracebackModule != NULL) {
177 PyObject *tbList, *emptyString, *strRetval;
178
179 tbList = PyObject_CallMethod(tracebackModule, (char*)"format_exception",
180 (char*)"OOO", type,
181 value == NULL ? Py_None : value,
182 traceback == NULL ? Py_None : traceback);
183
184 emptyString = PyUnicode_FromString("");
185 strRetval
186 = PyObject_CallMethod(emptyString, (char*)"join", (char*)"O", tbList);
187
188 error_string = strdup(PyUnicode_AsUTF8(strRetval));
189
190 Py_DECREF(tbList);
191 Py_DECREF(emptyString);
192 Py_DECREF(strRetval);
193 Py_DECREF(tracebackModule);
194 } else {
195 error_string = strdup("Unable to import traceback module.");
196 }
197 Py_DECREF(type);
198 Py_XDECREF(value);
199 Py_XDECREF(traceback);
200
201 free(error_string);
202 exit(1);
203 }
204
205
206 /**
207 * loadPlugin() and unloadPlugin() are entry points that are
208 * exported, so Bareos can directly call these two entry points
209 * they are common to all Bareos plugins.
210 *
211 * External entry point called by Bareos to "load" the plugin
212 */
loadPlugin(PluginApiDefinition * lbareos_plugin_interface_version,CoreFunctions * lbareos_core_functions,PluginInformation ** plugin_information,PluginFunctions ** plugin_functions)213 bRC loadPlugin(PluginApiDefinition* lbareos_plugin_interface_version,
214 CoreFunctions* lbareos_core_functions,
215 PluginInformation** plugin_information,
216 PluginFunctions** plugin_functions)
217 {
218 if (Py_IsInitialized()) { return bRC_Error; }
219
220 Py_InitializeEx(0);
221 // add bareos plugin path to python module search path
222 PyObject* sysPath = PySys_GetObject((char*)"path");
223 PyObject* pluginPath = PyUnicode_FromString(PLUGIN_DIR);
224 PyList_Append(sysPath, pluginPath);
225 Py_DECREF(pluginPath);
226
227 /* import the bareossd module */
228 PyObject* bareossdModule = PyImport_ImportModule("bareossd");
229 if (!bareossdModule) {
230 printf("loading of bareossd extension module failed\n");
231 if (PyErr_Occurred()) { PyErrorHandler(); }
232 }
233
234 /* import the CAPI from the bareossd python module
235 * afterwards, Bareossd_* macros are initialized to
236 * point to the corresponding functions in the bareossd python
237 * module */
238 import_bareossd();
239
240 /* set bareos_core_functions inside of barossd module */
241 Bareossd_set_bareos_core_functions(lbareos_core_functions);
242
243 bareos_core_functions
244 = lbareos_core_functions; /* Set Bareos funct pointers */
245 bareos_plugin_interface_version = lbareos_plugin_interface_version;
246
247 *plugin_information = &pluginInfo; /* Return pointer to our info */
248 *plugin_functions = &pluginFuncs; /* Return pointer to our functions */
249
250 #if PY_VERSION_HEX < VERSION_HEX(3, 7, 0)
251 PyEval_InitThreads();
252 #endif
253
254 mainThreadState = PyEval_SaveThread();
255 return bRC_OK;
256 }
257
258 /**
259 * Plugin called here when it is unloaded, normally when Bareos is going to
260 * exit.
261 */
unloadPlugin()262 bRC unloadPlugin()
263 {
264 /* Terminate Python if it was initialized correctly */
265 if (mainThreadState) {
266 PyEval_RestoreThread(mainThreadState);
267 Py_Finalize();
268 mainThreadState = nullptr;
269 }
270 return bRC_OK;
271 }
272
273 #ifdef __cplusplus
274 }
275 #endif
276
277 /* Create a new instance of the plugin i.e. allocate our private storage */
newPlugin(PluginContext * plugin_ctx)278 static bRC newPlugin(PluginContext* plugin_ctx)
279 {
280 struct plugin_private_context* plugin_priv_ctx
281 = (struct plugin_private_context*)malloc(
282 sizeof(struct plugin_private_context));
283 if (!plugin_priv_ctx) { return bRC_Error; }
284 memset(plugin_priv_ctx, 0, sizeof(struct plugin_private_context));
285 plugin_ctx->plugin_private_context
286 = (void*)plugin_priv_ctx; /* set our context pointer */
287
288 /* set bareos_plugin_context inside of barossd module */
289 Bareossd_set_plugin_context(plugin_ctx);
290 /* For each plugin instance we instantiate a new Python interpreter. */
291 PyEval_AcquireThread(mainThreadState);
292 plugin_priv_ctx->interpreter = Py_NewInterpreter();
293 PyEval_ReleaseThread(plugin_priv_ctx->interpreter);
294
295 /*
296 * Always register some events the python plugin itself can register
297 * any other events it is interested in.
298 */
299 bareos_core_functions->registerBareosEvents(plugin_ctx, 1,
300 bSdEventNewPluginOptions);
301
302 return bRC_OK;
303 }
304
305 /* Free a plugin instance, i.e. release our private storage */
freePlugin(PluginContext * plugin_ctx)306 static bRC freePlugin(PluginContext* plugin_ctx)
307 {
308 struct plugin_private_context* plugin_priv_ctx
309 = (struct plugin_private_context*)plugin_ctx->plugin_private_context;
310
311 if (!plugin_priv_ctx) { return bRC_Error; }
312
313 /*
314 * Stop any sub interpreter started per plugin instance.
315 */
316 PyEval_AcquireThread(plugin_priv_ctx->interpreter);
317
318
319 if (plugin_priv_ctx->pModule) { Py_DECREF(plugin_priv_ctx->pModule); }
320
321 Py_EndInterpreter(plugin_priv_ctx->interpreter);
322 #if PY_VERSION_HEX < VERSION_HEX(3, 2, 0)
323 PyEval_ReleaseLock();
324 #else
325 PyThreadState_Swap(mainThreadState);
326 PyEval_ReleaseThread(mainThreadState);
327 #endif
328
329 free(plugin_priv_ctx);
330 plugin_ctx->plugin_private_context = NULL;
331
332 return bRC_OK;
333 }
334
335
handlePluginEvent(PluginContext * plugin_ctx,bSdEvent * event,void * value)336 static bRC handlePluginEvent(PluginContext* plugin_ctx,
337 bSdEvent* event,
338 void* value)
339 {
340 bRC retval = bRC_Error;
341 bool event_dispatched = false;
342 PoolMem plugin_options(PM_FNAME);
343 plugin_private_context* plugin_priv_ctx
344 = (plugin_private_context*)plugin_ctx->plugin_private_context;
345
346 if (!plugin_priv_ctx) { goto bail_out; }
347
348 /*
349 * First handle some events internally before calling python if it
350 * want to do some special handling on the event triggered.
351 */
352 switch (event->eventType) {
353 case bSdEventNewPluginOptions:
354 event_dispatched = true;
355 retval = parse_plugin_definition(plugin_ctx, value, plugin_options);
356 break;
357 default:
358 break;
359 }
360
361 /*
362 * See if we have been triggered in the previous switch if not we have to
363 * always dispatch the event. If we already processed the event internally
364 * we only do a dispatch to the python entry point when that internal
365 * processing was successful (e.g. retval == bRC_OK).
366 */
367 if (!event_dispatched || retval == bRC_OK) {
368 PyEval_AcquireThread(plugin_priv_ctx->interpreter);
369
370 /*
371 * Now dispatch the event to Python.
372 * First the calls that need special handling.
373 */
374 switch (event->eventType) {
375 case bSdEventNewPluginOptions:
376 /*
377 * See if we already loaded the Python modules.
378 */
379 if (!plugin_priv_ctx->python_loaded) {
380 retval = PyLoadModule(plugin_ctx, plugin_options.c_str());
381 }
382
383 /* Only try to call when the loading succeeded. */
384 if (retval == bRC_OK) {
385 retval = Bareossd_PyParsePluginDefinition(plugin_ctx,
386 plugin_options.c_str());
387 }
388 break;
389 default:
390 /*
391 * Handle the generic events e.g. the ones which are just passed on.
392 * We only try to call Python when we loaded the right module until
393 * that time we pretend the call succeeded.
394 */
395 if (plugin_priv_ctx->python_loaded) {
396 retval = Bareossd_PyHandlePluginEvent(plugin_ctx, event, value);
397 } else {
398 retval = bRC_OK;
399 }
400 break;
401 }
402
403 PyEval_ReleaseThread(plugin_priv_ctx->interpreter);
404 }
405
406 bail_out:
407 return retval;
408 }
409
410 /**
411 * Parse the plugin definition passed in.
412 *
413 * The definition is in this form:
414 *
415 * python:module_path=<path>:module_name=<python_module_name>:...
416 */
parse_plugin_definition(PluginContext * plugin_ctx,void * value,PoolMem & plugin_options)417 static bRC parse_plugin_definition(PluginContext* plugin_ctx,
418 void* value,
419 PoolMem& plugin_options)
420 {
421 bool found;
422 int i, cnt;
423 PoolMem plugin_definition(PM_FNAME);
424 char *bp, *argument, *argument_value;
425 plugin_private_context* plugin_priv_ctx
426 = (plugin_private_context*)plugin_ctx->plugin_private_context;
427
428 if (!value) { return bRC_Error; }
429
430 /*
431 * Parse the plugin definition.
432 * Make a private copy of the whole string.
433 */
434 PmStrcpy(plugin_definition, (char*)value);
435
436 bp = strchr(plugin_definition.c_str(), ':');
437 if (!bp) {
438 Jmsg(plugin_ctx, M_FATAL, LOGPREFIX "Illegal plugin definition %s\n",
439 plugin_definition.c_str());
440 Dmsg(plugin_ctx, debuglevel, LOGPREFIX "Illegal plugin definition %s\n",
441 plugin_definition.c_str());
442 goto bail_out;
443 }
444
445 /*
446 * Skip the first ':'
447 */
448 bp++;
449
450 cnt = 0;
451 while (bp) {
452 if (strlen(bp) == 0) { break; }
453
454 /*
455 * Each argument is in the form:
456 * <argument> = <argument_value>
457 *
458 * So we setup the right pointers here, argument to the beginning
459 * of the argument, argument_value to the beginning of the argument_value.
460 */
461 argument = bp;
462 argument_value = strchr(bp, '=');
463 if (!argument_value) {
464 Jmsg(plugin_ctx, M_FATAL, LOGPREFIX "Illegal argument %s without value\n",
465 argument);
466 Dmsg(plugin_ctx, debuglevel,
467 LOGPREFIX "Illegal argument %s without value\n", argument);
468 goto bail_out;
469 }
470 *argument_value++ = '\0';
471
472 /*
473 * See if there are more arguments and setup for the next run.
474 */
475 bp = argument_value;
476 do {
477 bp = strchr(bp, ':');
478 if (bp) {
479 if (*(bp - 1) != '\\') {
480 *bp++ = '\0';
481 break;
482 } else {
483 bp++;
484 }
485 }
486 } while (bp);
487
488 found = false;
489 for (i = 0; plugin_arguments[i].name; i++) {
490 if (Bstrcasecmp(argument, plugin_arguments[i].name)) {
491 int64_t* int_destination = NULL;
492 char** str_destination = NULL;
493 bool* bool_destination = NULL;
494
495 switch (plugin_arguments[i].type) {
496 case argument_instance:
497 int_destination = &plugin_priv_ctx->instance;
498 break;
499 case argument_module_path:
500 str_destination = &plugin_priv_ctx->module_path;
501 break;
502 case argument_module_name:
503 str_destination = &plugin_priv_ctx->module_name;
504 break;
505 default:
506 break;
507 }
508
509 if (int_destination) {
510 *int_destination = parse_integer(argument_value);
511 }
512
513 if (str_destination) { SetString(str_destination, argument_value); }
514
515 if (bool_destination) {
516 *bool_destination = ParseBoolean(argument_value);
517 }
518
519 /*
520 * When we have a match break the loop.
521 */
522 found = true;
523 break;
524 }
525 }
526
527 /*
528 * If we didn't consume this parameter we add it to the plugin_options list.
529 */
530 if (!found) {
531 PoolMem option(PM_FNAME);
532
533 if (cnt) {
534 Mmsg(option, ":%s=%s", argument, argument_value);
535 PmStrcat(plugin_options, option.c_str());
536 } else {
537 Mmsg(option, "%s=%s", argument, argument_value);
538 PmStrcat(plugin_options, option.c_str());
539 }
540 cnt++;
541 }
542 }
543
544 if (cnt > 0) { PmStrcat(plugin_options, ":"); }
545
546 return bRC_OK;
547
548 bail_out:
549 return bRC_Error;
550 }
551
552 /**
553 * Initial load of the Python module.
554 *
555 * Based on the parsed plugin options we set some prerequisites like the
556 * module path and the module to load. We also load the dictionary used
557 * for looking up the Python methods.
558 */
PyLoadModule(PluginContext * plugin_ctx,void * value)559 static bRC PyLoadModule(PluginContext* plugin_ctx, void* value)
560 {
561 bRC retval = bRC_Error;
562 struct plugin_private_context* plugin_priv_ctx
563 = (struct plugin_private_context*)plugin_ctx->plugin_private_context;
564 PyObject *sysPath, *mPath, *pName, *pFunc;
565 /* See if we already setup the python search path. */
566 if (!plugin_priv_ctx->python_path_set) {
567 /* Extend the Python search path with the given module_path */
568 if (plugin_priv_ctx->module_path) {
569 sysPath = PySys_GetObject((char*)"path");
570 mPath = PyUnicode_FromString(plugin_priv_ctx->module_path);
571 PyList_Append(sysPath, mPath);
572 Py_DECREF(mPath);
573 plugin_priv_ctx->python_path_set = true;
574 }
575 }
576
577 /* Try to load the Python module by name. */
578 if (plugin_priv_ctx->module_name) {
579 Dmsg(plugin_ctx, debuglevel,
580 LOGPREFIX "Trying to load module with name %s\n",
581 plugin_priv_ctx->module_name);
582 pName = PyUnicode_FromString(plugin_priv_ctx->module_name);
583 plugin_priv_ctx->pModule = PyImport_Import(pName);
584 Py_DECREF(pName);
585
586 if (!plugin_priv_ctx->pModule) {
587 Dmsg(plugin_ctx, debuglevel,
588 LOGPREFIX "Failed to load module with name %s\n",
589 plugin_priv_ctx->module_name);
590 goto bail_out;
591 }
592
593 Dmsg(plugin_ctx, debuglevel,
594 LOGPREFIX "Successfully loaded module with name %s\n",
595 plugin_priv_ctx->module_name);
596
597 /* Get the Python dictionary for lookups in the Python namespace. */
598 plugin_priv_ctx->pyModuleFunctionsDict
599 = PyModule_GetDict(plugin_priv_ctx->pModule); /* Borrowed reference */
600
601
602 /* Lookup the load_bareos_plugin() function in the python module. */
603 pFunc = PyDict_GetItemString(plugin_priv_ctx->pyModuleFunctionsDict,
604 "load_bareos_plugin"); /* Borrowed reference */
605 if (pFunc && PyCallable_Check(pFunc)) {
606 PyObject *pPluginDefinition, *pRetVal;
607
608 pPluginDefinition = PyUnicode_FromString((char*)value);
609 if (!pPluginDefinition) { goto bail_out; }
610
611 pRetVal = PyObject_CallFunctionObjArgs(pFunc, pPluginDefinition, NULL);
612 Py_DECREF(pPluginDefinition);
613
614 if (!pRetVal) {
615 goto bail_out;
616 } else {
617 retval = ConvertPythonRetvalTobRCRetval(pRetVal);
618 Py_DECREF(pRetVal);
619 }
620 } else {
621 Dmsg(plugin_ctx, debuglevel,
622 LOGPREFIX "Failed to find function named load_bareos_plugin()\n");
623 goto bail_out;
624 }
625
626 /*
627 * Keep track we successfully loaded.
628 */
629 plugin_priv_ctx->python_loaded = true;
630 }
631
632 return retval;
633
634 bail_out:
635 if (PyErr_Occurred()) { PyErrorHandler(plugin_ctx, M_FATAL); }
636
637 return retval;
638 }
639
640 } /* namespace storagedaemon*/
641